前途科技前途科技
  • 服务
  • 关于
  • AI
    • AI 大模型
    • 具身智能
    • 算力芯片
  • 科技
    • 智能终端
    • 软件·互联网
    • 汽车·出行
    • 科学前沿
  • 资源中心
    • 深度研究
      • AI 前沿
      • 教程
      • AI 知识库
      • 案例研究
    • 行业报告
      • 白皮书
      • 行业报告
      • 研究报告
      • 技术分享
      • 专题报告
    • 精选案例
      • 金融行业
      • 医疗行业
      • 教育行业
      • 零售行业
      • 制造行业
  • 服务
  • 关于
联系我们
教程/09.17 · 00:00/5 MIN/作者:苏晚/1 阅读

无 NCCL 也能训 RL:HF Jobs 上用一个桶、一个代理,把训练和推理拆到两台机器

Hugging Face 的 Amine Dirhoussi 等人展示了在 HF Jobs 上跑异步 GRPO 的完整工程细节:LoRA 只同步适配器、共享存储桶替代 NCCL、代理做 KV 缓存亲和路由。同一份 recipe 从 3 小时 27 分钟压到 53 分钟,MFU 从 3.9% 提到 23.5%——每一步的诊断路径都写进了指标里。

本文基于 Hugging Face 团队 2026 年 9 月 10 日发表的《Async GRPO with LoRA across HF Jobs: a bucket, a proxy, and no NCCL》(作者 Amine Dirhoussi、Quentin Gallouédec、Kashif Rasul、Sergio Paniego),原文见 huggingface.co/blog/asyncgrpo-lora-hfjobs。我在原文之外接入了 Thinking Machines「LoRA Without Regret」的信息论论证,用来解释为什么整套架构成立的物理前提是 rank-1 就够——不理解这一步,后面的桶和代理都只是奇技淫巧。

一句话:把 GB 级的权重同步换成 MB 级的适配器同步,一切都变得可能

强化学习训练最痛的地方在于「训练器和采样器必须紧耦合」。GRPO 每几个优化器步就要把最新权重推给推理端,让 rollout 用得上最新策略。传统做法是训练和 vLLM 共享节点,靠 NCCL 或本地文件系统直接搬 tensor。可一旦搬到「一个容器一台机器」的云环境(比如 Hugging Face Jobs),跨节点没 NCCL、没共享磁盘、连 localhost 都不通,这套逻辑瞬间失效。每次同步搬 3GB 的权重穿越公网,训 500 步花几小时都不够。

HF 团队的解法看起来很朴素:只训 LoRA 适配器,只同步适配器。rank-1 的 LoRA 适配器对于 1.5B 模型只有几 MB,走一个挂在所有 Job 上的存储桶就行——桶就是那个「延迟一点、但够用」的共享文件系统。5 次调参跑下来,同样 500 步的训练从 3 小时 27 分压到 53 分,模型 FLOPs 利用率(MFU)从 3.9% 拉到 23.5%,reward 曲线几乎完全重合。这不是「快了一点」的量级,是 3.9 倍。

异步 GRPO + LoRA 在 HF Jobs 上的三 Job 架构:训练 Job 里跑 AsyncGRPOTrainer 和代理,两个 vLLM Job 各占一张卡,一个存储桶挂在三者同一个路径下

架构图。TRL 只跟本机的代理讲话,代理转发到两台 vLLM,适配器目录走桶。图片来源:Hugging Face 官方博客

第一步:搞明白为什么 rank-1 不吃亏

如果 rank-1 只是「凑合能用」,这套架构根本没意义——每次同步几百 MB 的中等秩适配器一样搬不动。所以 Thinking Machines 那篇《LoRA Without Regret》是这里绕不过去的前提。他们在 MATH、GSM8K、DeepMath 这几个数学推理任务上,用 policy gradient 类算法(就是 GRPO 家族)系统对比了 LoRA 和 full fine-tuning,结论是:即便 rank 低到 1,LoRA 在 RL 上也能追平 full fine-tuning 的表现。

证据里最有说服力的一环是信息论论证:每一个 RL episode 通过 advantage function 大约只提供 O(1) 比特的有效学习信号——不像 SFT 每 episode 提供 O(tokens) 比特。他们估算 MATH 数据集上 rank-1 LoRA 对 1.5B 模型有 300 万参数量,即便按每参数 1 比特信息容量算,整个训练过程的总信号量也远远填不满这些参数。换句话说:瓶颈是信号量,不是容量。你上更高的 rank,多出来的容量只是空转。

理解了这一层,HF 团队接下来的所有工程选择才有依据:能拿 rank-1 打天下,就不用担心桶传得慢、担心适配器多占内存、担心跨机搬运的开销——这些都被压到工程可接受的范围内。

三个 Job、一个桶:架构本身其实很小

整个系统就四个组件:

训练 Job:跑 AsyncGRPOTrainer(TRL v1.14 起支持 LoRA-only 同步,PR #7017),h200×2 双卡 FSDP。每几个优化器步(默认 4 步)保存一次适配器到 <output_dir>/.vllm_lora/trl-policy-v{N},原子改名发布,然后 POST 到 vLLM 的 /v1/load_lora_adapter 端点。

vLLM 副本:两个(或三个)独立 Job,各占一张 H200,跑 vllm/vllm-openai:v0.27.1 镜像。启动时开三个开关:--enable-lora、--max-lora-rank 1、--max-loras 6。为什么是 6?因为 max_staleness=4,训练器要求 vLLM 同时挂载「当前策略 + 前 4 个版本」共 5 份适配器,加上换版本瞬间需要多一个槽位承接新版,所以是 6。少一个,vLLM 就会在同步瞬间静默驱逐一个仍有 rollout 在跑的策略版本。

存储桶:通过 hf-mount 以 FUSE 文件系统的形式挂在三个 Job 的同一路径 /lora 下。训练器写入,vLLM 只读挂载。TRL 和 vLLM 的代码一个字都没改——它俩都以为在跟本地磁盘打交道。

代理:跑在训练 Job 的 127.0.0.1:8000,TRL 只跟它讲话。

这里有一个非常容易踩的坑,几乎所有想复用 vLLM 推理端点的团队都会独立重新发现它一遍:适配器必须带版本号。vLLM 的前缀缓存(prefix cache)是按适配器名字做键的。如果每次都用同一个名字覆盖,权重变了但同名的旧 KV blocks 还在缓存里匹配得上——一个 rollout 的 prefill 走的是老版本、decode 走的是新版本,训练器完全察觉不到,只会看到 ratio 指标从 1.0 慢慢漂移。带版本号(、)就是让「一个名字永远只对应一份权重」,缓存要么命中要么整段作废,永远不会串。

标签:LoRA强化学习Hugging Face
苏晚
苏晚Su Wan

前途科技 · 前沿主笔

关注 AI 与前沿科技的观察者,前途科技前沿内容主笔。

查看全部文章

想了解 AI 如何助力您的企业?

免费获取企业 AI 成熟度诊断报告,发现转型机会

//

24小时热榜

OpenAI发布对齐失效报告框架,曝光模型偷盗密钥与伪造数据
TOP1

OpenAI发布对齐失效报告框架,曝光模型偷盗密钥与伪造数据

OpenAI推ChatGPT广告新功能:支持赞助智能体与Shopify
TOP2

OpenAI推ChatGPT广告新功能:支持赞助智能体与Shopify

3

Nvidia 宣布支持原生 GPU 内核 Rust 编程,直接编译到 PTX

12小时前
Nvidia 宣布支持原生 GPU 内核 Rust 编程,直接编译到 PTX
4

Google Home 接入 MCP 标准,AI Agent 可控智能家居

前途科技前途科技
服务关于快讯技术商业报告
前途科技微信公众号

微信公众号

扫码关注

Copyright © 2026 AccessPath.com, 前途国际科技咨询(北京)有限公司,版权所有。|京ICP备17045010号-1|京公网安备 11010502033860号|隐私政策|服务条款
trl-policy-v1
trl-policy-v2

代理为什么必须存在:不只是加个 header

如果只是加鉴权头,代理没必要专门写。它真正必要,是因为有两件事只有代理能做:

广播加载:适配器要在两台 vLLM 上同时可用,才能让 TRL 假装自己面对一个逻辑上的推理服务器。代理的 /v1/load_lora_adapter 走的是 all-or-nothing 语义:任何一台加载失败,已经加载成功的那台要马上 unload 回滚,返回 504。原因是 FUSE 挂载偶尔会有传播延迟,第一次没读到很正常,代理里带一个 0.5 秒的重试就能把 252 次加载做到 100% 成功——其中 246 次一遍过,6 次要第二次。

KV 缓存亲和路由:这是全篇最工程化的一段。vLLM 内部把 KV cache 按 16-token 的块(block)粒度缓存,同一个前缀已经算过的块下次可以复用,跳过 prefill。GRPO 训练里同一个 prompt 会被采样 8 次,如果 8 次全都路由到同一台 vLLM,后面 7 次 prefill 就能直接命中。代理的路由算法是这样的:

  1. 把请求的 token 序列切成 16-token 块,从种子(适配器名)开始做链式 blake2b 哈希。为什么必须链式?因为 KV cache 里第 3 块的值是否有效依赖前 1、2 块完全一致,中间任何一个 token 不同,第 3 块的哈希就应该不同。
  2. 每个块的哈希被记录:哪些副本已经 serve 过、有哪些不同的后继块。如果一个块有多个后继,说明它是「大家共享的开头」(模板文本、system prompt),不能拿来判断谁「独占」了这个 prompt。
  3. 找到「独占块数」最多的副本。如果它当前 inflight 请求比最少的副本多不超过 8 个,走亲和;否则走溢出到最闲的那台。

跑完一轮 64,728 次 rollout 的分布是:亲和命中 84.5%(54,712 次),因为负载不均衡而放弃亲和的溢出 1.3%,全新 prompt 只能均匀分发的 14.2%——理论最小值是 12.5%(每 8 个 rollout 里必然有 1 个是首次出现)。这意味着大约 35,000 次 prefill 被完全跳过。

五轮跑:把「训快点」拆成可诊断的路径

这一段是这篇博客对做 RL 训练工程的读者最直接的启发。作者没有一开始就给最优参数,而是从一份「能跑但慢」的配置出发,一轮一轮读指标、改一个变量、再读指标。三个指标是他们的诊断三剑客:

  • sample/rollout_queue_size:训练器和采样器之间的缓冲队列,看它是满的还是空的
  • perf/rollout_wait_s:训练器等采样的时间
  • rollout/backpressure_s:采样器因为队列满而被憋住的时间

判断规则:队列满 + 训练器不等 + 采样器被憋 → 训练器慢;队列空 + 训练器在等 + 采样器不憋 → 采样器慢。

Run 1 vs Run 2:token 打包把每一行装 13 个样本、微批数从 64 降到 6、每步时间从 23 秒压到 5.9 秒、生成吞吐从 4.2k 冲到 27.5k tok/s

Run 2 上 token budget packing 的效果。图片来源:Hugging Face 官方博客

Run 1(基线):3h 27min,MFU 3.9%。每步 22.9 秒,前向+反向 21.9 秒。队列 476/512 常满,采样器空转。判定:训练器是瓶颈。为什么这么慢?batch/samples_per_row=1.0,也就是一行只装一个样本、64 个微批。GPU 大量算力浪费在 padding 上。

Run 2:token_budget=16384、gradient_accumulation_steps=6。让每一行装满 token 而不是每一行一个样本。结果:每行 12.7 个样本、微批 6 个、步时 5.9 秒、MFU 提到 19%。生成端从 4.6k tok/s 涨到 25k tok/s,副本终于不闲。这一步是 4 倍加速,也是全部 5 轮里收益最大的一步。

Run 3:关闭 gradient checkpointing。checkpointing 会在反向传播里重算前向,代价是「反向≈2×前向」。但 LoRA 训练里 base weights 冻结,反向可以只算适配器那部分。关掉后反向少 1 秒,队列从 420 掉到 71——瓶颈第一次翻到生成端。

Run 4:加第三个 vLLM 副本。但奇怪的是加速不明显,rollout/inflight 卡在 128。翻代码发现是客户端的 max_inflight_tasks=128 上限撞到了,不是 vLLM 满载。这里的教训是:加副本前先确认瓶颈是不是 GPU,不然只是给不用的机器付钱。

Run 5:把 max_inflight_tasks 提到 384、queue_maxsize 提到 768。三个副本各拿 128 并发。53 分钟跑完 500 步,MFU 23.5%,生成 27.5k tok/s。队列又满了,瓶颈又回到训练器。这时候再加副本就没意义了,只有换更多训练卡才有效。

五轮跑的 reward 曲线在挂钟时间上重合:run 5 在 52 分钟到达 run 1 需要 3 小时 26 分才到达的位置

同一个 recipe,同样的 reward 终点,run 5 用 52 分钟走完 run 1 需要 3 小时 26 分的路。图片来源:Hugging Face 官方博客

reward 曲线的重合本身是最重要的验证:这不是拿收敛质量换速度,是同一个训练结果、同一个终点、同一个 ratio≈1.000,只是把中间的等待都消灭了。

对中文语境的启发:三个可以带走的模式

第一,桶就是一个「延迟一点的共享文件系统」。这个视角本身就是一次心智解绑。绝大多数分布式训练的教程都在教你怎么在 NCCL、Ray、TorchDistributed 之间选,好像跨机通信必须是同步的、必须是 tensor 级的。这篇的思路是:如果我要同步的东西只有几 MB、每几秒同步一次,我完全可以用一个最普通的对象存储加一次原子改名把它变成一个软性的同步机制。它不 real-time、有几百毫秒延迟,但 GRPO 那种「大约每几步同步一次」的场景压根不需要 real-time。

第二,前缀缓存 + 版本号的问题会在任何复用推理服务器的训练场景里出现。不止 vLLM,任何带 KV cache 的推理系统都会碰到「权重更了、cache 没更」的情况。工程惯用的解法是要么显式把 cache 冲掉、要么让新旧策略走不同的命名空间。HF 团队选了后者——这个思路可以直接搬到 SGLang、TensorRT-LLM 上,只要你的推理服务器支持按 adapter/model 名做 cache 隔离。

第三,性能优化必须靠指标而不是靠感觉。5 轮里每一次改动都对应一个可测的现象:队列满不满、wait 长不长、backpressure 高不高。这三个数就是一台 RL 训练机的血压计。如果你的训练系统连这三个数都没有埋点,那么你所有的「跑起来慢」都只是猜测。这一点对国内很多快速搭起 RL pipeline 的团队尤其相关——很多系统跑到「能训出来」就停了,从来没测过 MFU、从来没看过队列长度,「慢」和「快」之间的路径根本无法复现。

想上手:能拿走的东西

HF 团队给了完整可跑的代码:github.com/AmineDiro/hfjobs-lora-buckets。快速验证只要 15 分钟(MAX_STEPS=20 ./run_all.sh --wait),跑完整 run 5 的最优配置 55 分钟左右,按 4×H200 的价位大约 18 美元一轮。

配套是三份文档:TRL 的 AsyncGRPOTrainer 文档写清楚了所有可观察的指标,Storage Buckets 讲桶怎么建,hf-mount 讲挂载的 FUSE 层。

如果你想自己 fork 出一份国产云版本,需要替换的只有两件事:一个能被多机以 FUSE 挂载的对象存储(阿里 OSS、腾讯 COS 都有类似能力,加个 s3fs 或 ossfs 就行),以及一个能像 HF Jobs 那样跨机器分配 GPU 容器的编排层(自建 K8s + gpu-operator 就够)。核心的三个组件——LoRA-only 同步、带版本号的适配器、KV 亲和路由——都是与云平台无关的,全部在 TRL/vLLM 侧。

一个观察

这篇博客最反常识的一点,其实不是技术。它花了大量篇幅记录了 5 轮跑的每一次弯路:一开始 MFU 只有 3.9%、加副本以为会快但被客户端限流卡住、以为需要更大 batch 但发现是 padding 浪费。大部分工程博客只写「最优解」,这一篇把「怎么找到最优解」摊在桌面上。这种写法比直接给一个最终参数表值钱得多——参数表 3 个月就过期,方法论不会。

12小时前
Google Home 接入 MCP 标准,AI Agent 可控智能家居
5

华为郭平:ICT 及计算领域目标是成为英伟达,不做自研大模型

12小时前
华为郭平:ICT 及计算领域目标是成为英伟达,不做自研大模型
6

研究突破三值大模型存储极限,新格式降至 1.485 bits/参数

12小时前
研究突破三值大模型存储极限,新格式降至 1.485 bits/参数
7

ChatGPT 上线分析套件:量化企业 AI 业务价值与回报

5小时前
ChatGPT 上线分析套件:量化企业 AI 业务价值与回报
8

4B 参数模型通过强化学习优化查询计划,比 Postgres 快 81%

12小时前
4B 参数模型通过强化学习优化查询计划,比 Postgres 快 81%
热门标签
大模型AgentRAG微调私有化部署Prompt EngineeringChatGPTClaudeDeepSeek智能客服知识管理内容生成代码辅助数据分析金融零售制造医疗教育AI 战略数字化转型ROI 分析OpenAIAnthropicGoogle

关注公众号

前途科技微信公众号

扫码关注,获取最新 AI 资讯

免费获取 AI 落地指南

3 步完成企业诊断,获取专属转型建议

已有 200+ 企业完成诊断