ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Agentic RL后训练资源分配优化:Libra动态调度提升3倍吞吐

Agentic RL后训练资源分配优化:Libra动态调度提升3倍吞吐 Agentic RL 后训练很多团队跑起来之后第一个问题不是模型不收敛而是 GPU 看起来都在用训练却一直很慢。问题往往出在资源分配采样要算力训练要显存奖励模型还要输出一个分数三类任务抢同一批卡抢得不好就是互相等。港中文和恒生大学近期提出的 Libra目标就是解决这个资源分配问题。从公开标题看它在“吞吐”这个指标上给出的结果是最高提升约 3 倍。这篇文章不讨论“有没有用”直接拆开它解决的是哪个环节、你要怎么在自己的训练环境里复现类似的效果。先说清楚这个东西到底是什么、适合谁。Libra 不是一个新的基座模型也不是一个直接拿来生成内容的推理工具它属于 Agentic RL 后训练的工程层面研究。Agentic RL 后训练需要把采样、策略更新、奖励评估串成一条流水线流水线里的任意一个阶段拖慢整体训练时间都会被拉长。Libra 的关注点就是把这条流水线的资源调度理顺让同一批 GPU 发挥更高的有效吞吐。文章后面会按下面几条线展开第一Agentic RL 后训练里的资源竞争到底发生在哪里第二Libra 这类资源分配方案的核心思路是什么第三在没有拿到官方源码之前你能用哪些通用方法验证“资源分配改一改吞吐会不会涨”第四给出调度策略实验的环境准备、配置模板、批量跑实验和问题排查方法。适合读者是正在做 LLM Agent 训练、强化学习后训练、或者是分布式训练平台维护的同学。下面直接进入正文。1. 核心能力速览先把 Libra 的定位和关键信息放在一张表里。需要注意当前输入材料只包含公开标题层面的信息所以表格里凡是涉及“是否开源”“启动方式”的地方都按“以项目官方发布为准”处理不替官方提前下结论。能力项说明项目名称Libra提出方香港中文大学、恒生大学以论文/公开资料为准所属方向Agentic RL 后训练、资源调度、分布式训练核心问题Agentic RL 后训练中采样、训练、评估等任务对 GPU 资源竞争激烈固定分配导致吞吐受限关键收益对外公开信息中吞吐最高提升约 3 倍主要对象策略模型、奖励模型、rollout 采样任务适用硬件GPU 集群多卡环境具体显存要求需按模型规模和论文设置启动方式需以项目官方发布为准本文给出通用训练资源分配实验脚手架是否支持 API论文研究阶段通常以训练日志和实验脚本为主接口能力待定是否支持批量任务训练实验天然支持多配置批量跑本文会给出批量实验脚本模板适合场景Agent 后训练、RLHF/RLVR 类训练、多阶段流水线训练性能调优一句话总结Libra 解决的是“GPU 资源在 Agentic RL 后训练多个阶段之间怎么分”的问题它的价值点不是单卡训练更快而是让整个训练流水线的有效产出更高。2. 问题Agentic RL 后训练的资源分配瓶颈Agentic RL 后训练和普通 SFT 不一样它不是一个“读数据 - 算损失 - 更新权重”的简单循环而是一个多智能体交互式的闭环。一次完整的训练循环通常包含下面几个环节策略模型根据当前任务输入生成多步推理或动作奖励模型或规则评估器对生成结果打分策略模型利用得分做策略梯度更新为了稳定训练还可能要维护参考模型和价值模型。这些环节分别对 GPU 资源提出了不同的需求。这里的关键冲突点在于各环节最贪心的硬件资源不一样。采样阶段需要高吞吐的文本生成它吃的是 GPU 算力、显存带宽和并发能力batch size 可以开得很大但不需要特别深的训练反向传播。策略训练阶段则需要大显存来放模型、优化器状态和梯度而且 forward 和 backward 都要占用算力。奖励评估和 Critic 模型虽然单次计算量不一定大但在多轮 Agent 任务里每一条轨迹都要被反复评估数量堆上去之后同样会成为瓶颈。如果这三类任务各自独占固定的 GPU 数量问题就会立刻暴露出来。假设你分配 4 张卡给 rollout 采样、4 张卡给策略训练、2 张卡给奖励评估只要采样速度跟不上训练速度训练卡就会进入等待状态GPU 利用率看起来不算低但有效产出并没有增加。反过来如果训练速度跟不上采样速度采样卡就会一直生成大量结果排队缓存越堆越高等到需要更新策略时这批缓存里的旧数据可能已经要作废了。这种“某一阶段等待、另一阶段超产”的情况在训练日志里非常难发现因为每张卡都在运行只不过很多卡在跑空循环或者等待队列。还有一个容易被忽略的问题Agentic RL 的轨迹长度不是固定的。智能体可能要执行 5 步也可能执行 50 步每一步都可能调用工具、读取环境反馈导致单个样本的耗时方差非常大。固定分配模式下采样端的并发度是死的遇到长轨迹就会整体拖慢而训练端根本不知道采样端发生了什么。反过来训练端的 batch 大小和更新频率也是死的采样结果多了也无法临时多分配一点计算资源来“加速消化”。这种动态不匹配正是 Libra 这类资源分配系统想解决的核心问题。衡量这个问题严重程度不要只看“GPU 利用率”要看“端到端吞吐”。端到端吞吐通常可以用单位时间内完成的策略更新次数、单位时间内处理完的有效轨迹数或者跑完固定数量训练 step 的总耗时长来表示。固定分配下任何一个阶段成为短板整体吞吐就由短板决定。效率高的时候是 70% 到 80%效率低的时候可能只有 30% 到 40%剩下的时间都消耗在跨阶段等待上。这也是为什么“吞吐最高提升 3 倍”这个数字有工程意义它说明原先的分配方案可能产生了大量的调度气泡。3. Libra 的思路资源怎么分才能提吞吐由于当前输入材料没有给出 Libra 的具体论文细节下面的内容会区分“从公开标题能确定的信息”和“结合同类系统设计做的合理推断”。先说能确定的信息Libra 的重点在“资源分配”而且优化的指标是“吞吐”。也就是说它大概率不是去改某个 loss 函数也不是去换一个更快的注意力实现而是把采样、训练、评估任务的资源配额放到一个更智能的调控框架里。再往下说合理推断。Agentic RL 后训练里的资源分配通常会有几个设计维度。第一按阶段划分资源池比如 rollout 池、训练池、评估池各池之间可以动态借用第二按流水线积压情况调整实例数比如训练端 backlog 大就把多余的采样卡让渡给训练第三设置任务的优先级和抢占策略防止某一个长尾任务卡住整条流水线第四结合可观测数据做预测提前调整资源配比而不是等队列堆起来再处理。Libra 大概率是在这些维度里找了一个比较好的动态调节策略。从“吞吐提升 3 倍”这个结果看可以进一步推测它重点解决的应该是动态资源借用。比如某一段时间内任务以长轨迹为主采样端的吞吐下降系统检测到训练端喂料不足就把一部分训练卡临时转换成采样任务等短轨迹样本多了训练端积压再把这部分卡调回来。这类“临时转换”不是只能靠物理换卡还可以通过同一批 GPU 上调度不同容器或不同进程来实现核心是避免固定分配。如果你要在自己的环境里验证这类思路最直接的方法就是写一个资源调度逻辑或者用框架自带的动态资源分配能力。以 Ray 为例你可以给 rollout 任务和训练任务各声明不同的 GPU 资源数量并在每个训练 step 结束后读取队列长度重新调整后续任务的数量。这个过程不需要立刻实现 Libra 的全部细节先把“固定分配”和“动态调整”两个版本的吞吐测出来就能有一个直观判断。当然也要说明Libra 的完整实现一定比通用脚本复杂得多。它可能还要考虑到模型权重如何在不同任务间切换、显存如何重新划分、通信开销会不会抵消收益、调度器本身会不会成为新的瓶颈。参考层面看动态调度是一个收益上限很高、但工程复杂度也不低的方案。验证时不要只看第一轮实验要多跑几轮取稳定的中间值避免被调度器抖动影响判断。4. 前置条件与环境准备在复现或验证 Agentic RL 后训练资源分配方案之前环境准备可以按下面的通用清单来做。具体版本要以你使用的训练框架和模型代码为准不需要照抄。硬件方面建议至少准备多卡 GPU 环境。单卡也可以验证思想和观察指标但无法模拟“资源分配”带来的真实收益因为资源分配的核心是把多张卡在不同任务之间重新划分。显存大小取决于你的策略模型和奖励模型规模如果做 7B 到 13B 级别的实验单卡至少需要 24G 以上并且训练时建议开启 ZeRO 或梯度检查点来降低显存压力。CPU 内存建议不低于 128G因为 rollout 阶段会缓存大量生成结果。磁盘方面要预留模型权重、数据集、日志和临时缓存的空间根据实际模型大小评估。操作系统建议使用 Linux常见发行版都可以。CUDA 驱动和 PyTorch 版本需要匹配你使用的 GPU。如果你用 Ray 做资源调度需要安装 Ray 客户端和对应版本的依赖。如果你用 DeepSpeed 做训练还需要安装 DeepSpeed 并确认它与 PyTorch 版本兼容。rollout 采样如果用 vLLM 或 SGLang 这类推理引擎要单独确认它们支持的模型格式和量化方式。不要忽视监控工具。资源分配实验没有监控就等于没有眼睛。推荐使用 nvidia-smi 做即时查看用 Ray Dashboard 查看任务和资源分配情况用 Prometheus Grafana 做长时间记录。下面写一段最简单的即时监控命令方便你在实验时随时看显存和算力占用# 每 2 秒刷新一次 GPU 状态 watch -n 2 nvidia-smi # 查看详细利用率、显存、温度、功耗 nvidia-smi dmon -s pucmet -d 2如果希望记录训练进程每个阶段的等待时间比较简单的做法是在脚本里打印带时间戳的日志再用 grep 过滤关键事件。下面是一个日志标记示例# 训练脚本内部打印的关键事件 # [2025-06-01 10:00:00.123] [trainer] step 10 start # [2025-06-01 10:00:00.456] [trainer] step 10 wait_for_rollout # [2025-06-01 10:00:05.789] [trainer] step 10 finish环境准备完成后先跑一个最简单的“单阶段冒烟测试”确认模型可以正常加载、数据可以正常读取再进入资源分配实验。不要一上来就起整个分布式集群否则问题很难定位。5. 搭建最小实验环境部署与配置示例在 Libra 官方仓库还没有公开的部署命令之前这里给出一套通用的 Agentic RL 后训练资源分配实验脚手架。你拿到官方代码后把其中“模型路径”“GPU 配额”“训练参数”替换成实际值即可。先看目录结构agentic_rl_exp/ ├── configs/ │ ├── fixed.yaml │ └── dynamic.yaml ├── scripts/ │ ├── start_fixed.sh │ ├── start_dynamic.sh │ └── run_batch.sh ├── src/ │ ├── rollout.py │ ├── trainer.py │ ├── reward.py │ └── scheduler.py ├── logs/ ├── outputs/ └── README.md这里的关键不是目录有多复杂而是要把“固定分配”和“动态分配”两种策略隔离成两套配置方便对比。下面给一个配置文件模板里面用gpu_quota表示不同阶段的 GPU 数量# configs/fixed.yaml model: policy: /path/to/policy_model reward: /path/to/reward_model resources: rollout_gpus: 4 train_gpus: 4 reward_gpus: 2 train: batch_size: 8 grad_accum_steps: 2 max_steps: 1000 rollout: max_turns: 20 temperature: 0.8动态配置则可以在resources里加入一个调度策略描述比如允许rollout_pool和train_pool之间按队列长度互借# configs/dynamic.yaml scheduler: enabled: true adjust_interval_steps: 5 queue_weight: 0.6 min_rollout_gpus: 2 min_train_gpus: 2 max_rollout_gpus: 8启动脚本按照配置读取参数然后用分布式训练框架拉起服务。下面给一个 shell 脚本模板注意路径需要按实际项目替换# scripts/start_fixed.sh export DATA_DIR./data export CONFIG_PATH./configs/fixed.yaml python -m torch.distributed.run \ --nproc_per_node8 \ src/trainer.py \ --config $CONFIG_PATH \ --output_dir ./outputs/fixed \ --log_file ./logs/fixed.log如果你使用 Ray 而不是纯 torch.distributed脚本可以改成# scripts/start_dynamic.sh export DATA_DIR./data export CONFIG_PATH./configs/dynamic.yaml ray start --head --port6379 python src/scheduler.py \ --config $CONFIG_PATH \ --output_dir ./outputs/dynamic \ --log_file ./logs/dynamic.log需要说明这套代码不是 Libra 的实现只是验证“资源分配策略是否有影响”的最小骨架。核心思想是固定策略下所有进程的资源配额保持不动动态策略下调度器每隔 N 步读取一次队列长度决定是否把某类任务缩容把资源让给当前积压最多的阶段。6. 功能测试与吞吐验证在跑真实训练之前先用一个小规模任务验证整体流程能走通。选择一个较小的策略模型或一个缩短的评测任务确认采样、训练、奖励评估三个阶段都能正常执行并产出日志。冒烟测试的判断标准是日志中出现完整的 step 更新记录采样进程能持续生成 rollout奖励评估进程能返回分数模型权重能正常保存。冒烟测试通过后再进入吞吐对比实验。建议按下面的流程操作先跑固定分配策略。使用fixed.yaml配置启动训练脚本记录三个数字总耗时、每秒完成的有效样本数、GPU 空闲时间占比。这里的“有效样本数”要过滤掉因等待或重试产生的无效样本。然后把日志归档比如命名为baseline_run_1.log。重复至少 3 次取中位数避免单次抖动影响判断。再跑动态分配策略。使用dynamic.yaml配置启动动态调度脚本记录同样的指标。关键观察点是调度器是否真的发生了资源调整。一个简单的日志输出示例如下[2025-06-01 10:01:00] step10 rollout_queue15 train_backlog3 keep rollout4 train4 [2025-06-01 10:02:00] step15 rollout_queue6 train_backlog9 adjust rollout3 train5如果日志里没有任何调整动作说明队列长度没有触发阈值或者调度逻辑没有生效。这时要先检查队列长度指标是否被正确统计。除了真实训练也可以用一个极简的模拟脚本验证“固定分配 vs 动态调整”对端到端耗时的影响。下面这段 Python 代码模拟了两个阶段互相等待的情况动态策略在检测到队列积压后会将资源转移import time def simulate(fixed: bool, total_steps: int 100): rollout_gpu 4 train_gpu 4 queue 0 elapsed 0 for step in range(total_steps): # rollout 阶段每个 GPU 每秒产生 10 个样本动态模式下部分资源可以转移 produce_speed 10 * rollout_gpu queue produce_speed # train 阶段每个 GPU 每秒消耗 8 个样本 consume_speed 8 * train_gpu consumed min(queue, consume_speed) queue - consumed # 动态模式每 5 步根据队列长度调整一次资源配额 if not fixed and step % 5 0 and step 0: if queue 30 and rollout_gpu 2: rollout_gpu - 1 train_gpu 1 elif queue 5 and train_gpu 2: train_gpu - 1 rollout_gpu 1 time.sleep(0.01) elapsed 1 return elapsed fixed_time simulate(fixedTrue) dynamic_time simulate(fixedFalse) print(ffixed total_steps: {fixed_time}) print(fdynamic total_steps: {dynamic_time})这个模拟非常简化只说明一个问题当采样和训练速度不匹配时固定配额会产生累积队列或饥饿动态调整则能让两端尽量平衡。真实训练中还要考虑通信开销、显存分配、模型切换成本所以模拟结果不能直接等同于真实收益。建议以真实训练日志为准。判断实验是否成功重点比较三个指标。总耗时有明显下降说明调度策略起了作用。有效吞吐提升说明单位时间内完成的有效工作变多。GPU 空闲等待占比下降说明资源气泡被压缩。如果三项指标都没有变化就要检查是不是存在其他瓶颈比如单卡算力已经跑满、通信带宽受限、数据加载过慢。7. 批量实验与接口对接资源分配实验通常要跑多组配置而不是只跑两个配置文件。比如你希望对比“固定 442”“动态最小 228”“动态最小 336”等不同配置就需要一个批量实验脚本把不同配置依次跑完并保存日志。下面给一个 Python 批量启动脚本模板import subprocess import time configs [ configs/fixed.yaml, configs/dynamic_min2.yaml, configs/dynamic_min3.yaml, ] for cfg in configs: print(frun with {cfg}) proc subprocess.run( [bash, scripts/start_dynamic.sh, --config, cfg], capture_outputTrue, textTrue, ) time.sleep(5) with open(flogs/{cfg.split(/)[-1]}.log, w) as f: f.write(proc.stdout) f.write(proc.stderr)批量跑实验时要注意日志命名不能冲突建议把配置名、时间戳和重复次数都拼进文件名。每次实验之间清理残留进程避免上一轮的采样进程还在占用显存影响下一轮结果。如果你用 Ray每次实验结束后用ray stop --force清理集群。如果你的训练系统需要和监控平台对接一个轻量做法是让训练脚本在关键节点通过 HTTP 回调上报指标。下面给一个简单的 HTTP 上报脚本你只需要在训练的每个 step 结束时调用它import requests METRICS_SERVER http://127.0.0.1:8000/metrics def report_metrics(step, rollout_queue, train_backlog, gpu_util): payload { step: step, rollout_queue: rollout_queue, train_backlog: train_backlog, gpu_util: gpu_util, } requests.post(METRICS_SERVER, jsonpayload, timeout5)这个接口不是给 Libra 用的它是一个通用的验证脚手架。如果你已经有了 Prometheus 采集体系也可以直接把指标暴露成/metrics格式让采集端自动拉取。重点是让资源分配过程中的队列长度、等待时间、资源配额可以被动记录这样后续优化才能有数据支撑。8.
返回列表