【Task 22】perf(hybrid): 降低纯文本 Hybrid-async 权重发布开销 - #211
Conversation
# ⚡ Performance ## Tune weight publication chunking - Add a reproducible two-GPU Qwen3-0.6B Hybrid-async recipe with a checked-in tiny dataset. - Compare the unchanged reference/KL workload at 512 MiB and 1 GiB weight-update buffers across three paired trials. --- # ✅ Tests - Add analyzer tests for metric disambiguation, completion intervals, GPU windows, and non-finite guards. --- # 📝 Documentation - Document fixed workload, environment capture, reproduction, rollback, and report generation.
# ⚡ Performance ## Add staged Hybrid-async comparisons - Isolate the unused zero-KL reference path from batching gains - Tune train and log-prob token budgets independently - Fix formal runs at 20 steps and measure logged steps 5-15 --- # ✅ Tests ## Guard the benchmark analysis - Validate stable-window, GPU-memory, and microbatch parsing - Enforce fixed workload, paired seeds, and variant manifests --- # 📝 Documentation ## Document acceptance and rollback - Freeze a five-percent throughput target - Record three-way trial ordering and raw artifact paths
# ⚡ Performance ## Record the three-stage batching experiment - Compare the original baseline, zero-KL fix, and role-specific token budgets - Preserve three paired 20-step trials with metrics from steps 5 through 15 - Document the rejected token-budget direction and its actor-train regression --- # 🐛 Bug Fix ## Generate portable benchmark CSV files - Use LF line endings so generated results pass repository whitespace checks
# ⚡ Performance ## Honor weight publication intervals in Hybrid mode - Publish rollout weights at configured completed-step boundaries - Skip rollout pause and resume coordination when publication is omitted - Force publication on the final step and before configured evaluation ## Define the Task 22 interval-two experiment - Compare baseline, zero-KL fix, and interval-two publication - Keep token budgets, buffer size, workload, and max staleness fixed - Preserve rejected buffer-size and token-budget directions in the report --- # ✅ Tests ## Cover Hybrid publication boundaries - Verify interval-one compatibility, interval-two cadence, and final sync - Verify conservative evaluation sync and invalid interval handling
# ⚡ Performance ## Record the formal three-stage benchmark - Compare original baseline, zero-KL fix, and interval-two publication - Report a 13.48 percent throughput gain for interval two over zero-KL - Preserve three paired trials and step 5 through 15 measurements --- # ✅ Tests ## Strengthen result validation - Require 20 complete steps and 640 samples in every component run - Validate observed weight-publication counts against each configured interval - Report TIS, clipping, non-finite values, and runtime-error guardrails Co-authored-by: zheself <3010086230@qq.com>
Co-authored-by: zheself <3010086230@qq.com>
Co-authored-by: zheself <3010086230@qq.com>
Co-authored-by: zheself <3010086230@qq.com>
Co-authored-by: zheself <3010086230@qq.com>
Task 22 实验附件说明本附件为 Task 22 三组配对实验的完整交付包
主要结论
校验值复现与边界附件实验固定 Qwen3-0.6B、2× NVIDIA RTX PRO 6000 Blackwell、16 条 GSM8K 数据、有效 batch 32、response cap 512 和 step 5-15 稳定窗口。该实验只证明固定 2 GPU 小模型配置下的短周期性能收益,不外推到 Qwen3-4B/8 GPU,也不证明 interval-two 与 interval-one 的长期收敛等价。 复现命令: MODEL_PATH=/path/to/Qwen3-0.6B CUDA_VISIBLE_DEVICES=2,3 TOTAL_TRIALS=3 \
bash benchmarks/task22_hybrid_async_text/run_paired_trials.sh |
|
实现方面可能有以下问题:
|
Task 22 评审修复与 v4 复测已在 对评审意见的处理
复测结果
新证据:Task 22 v4 release assets,报告、CSV、SVG 曲线和原始证据以 9 个独立 asset 发布。
|
|
以下两个问题可以检查下:
|
|
最新评审修复与复测 评审问题处理 复测结果
optimized / zero_kl:+13.71%;三次配对为 +14.10% / +13.00% / +14.03%; raw-evidence.tar.gz SHA-256:740f39d14c993d9240d646ddb6600ceff9c17c12810689ba765134f6c8efe2cf。 |
|
以下两个问题可以参考下,解决完建议合入: #1 pause_requested 置位过早,把可降级的 rollout 崩溃升级成全作业致命错误 位置:relax/backends/megatron/actor.py:1842(置位)、:1856-1861(补偿)、:1897-1898(抛错) while True: ConnectionError(进程已死、端口拒绝)能确定服务端没收到请求,rollout 不可能处于 paused,但这里一律按"pause 结果不确定"处理去发补偿 resume。而 rollout 已死 ⇒ 补偿必然失败 ⇒ rollout_resume_failed=True ⇒ 全 回归点:改之前同一场景走 actor_fwd_only = True,日志明写 "Will continue without rollout update for this step",训练继续、由 health manager 拉起 rollout。现在任何 rollout 附带代价:_end_rollout_weight_update 重试 3 次 × rollout_http_timeout(默认 120 s)+ 2 s sleep = 最坏约 362 s。这期间 rank 0 卡在 except 里,其余 rank 全部堵在下面的 dist.all_reduce 干等。 建议改法:
现有测试 test_hybrid_weight_publication_does_not_resume_before_pause_request 只覆盖了 get_serve_url 抛错,没覆盖 connection refused,建议补一条。 #2 Actor 的"发布判据"与 Rollout 的"评测判据"条件不同,靠隐式耦合才一致 位置:actor.py:108-136(_should_publish_hybrid_weights)vs relax/components/rollout.py:360-370(_should_eval)
两者今天结论相同,只因为本 PR 在两个不同地方分别把非 global dataset 的 num_rollout_per_epoch 强制成 None(engine/rollout/bootstrap.py:23 和 distributed/ray/placement_group.py:120)。任何一处将来漂了,Actor 建议改法:直接复用已有的 relax/utils/misc.py:91-111 should_run_periodic_action(rollout_id, interval, num_rollout_per_epoch, num_rollout) —— 它的 step = rollout_id + 1、(step % interval == 0) or (nrpe is 顺带解决另外两点:_should_publish_hybrid_weights 不再重复实现公共 helper;train_hybrid 里 _wait_for_previous_eval() 被挪进 if should_publish_weights: 之后所依赖的"发布判据 ⊇ |
|
对于rollout的修改,想确认一点: _should_eval 现在会在最后一步强制评测,影响所有 RL 模式(relax/components/rollout.py:365) 新代码给 should_run_periodic_action 传了第 4 个参数 self.config.num_rollout,该 helper 内含: if num_rollout is not None and rollout_id == num_rollout - 1: 旧 _should_eval 没有这条。举例 eval_interval=10, num_rollout=25:旧的在 rollout_id 9/19 评测,新的额外在 24 也评。而 _should_eval 是 Rollout 组件的公共方法,sync colocate / fully_async / hybrid 都走它 —— 新测试没有覆盖到这个变化:test_rollout_evaluation_uses_periodic_and_epoch_boundaries 用 eval_interval=10, nrpe=4, num_rollout=20,_should_eval(19) 走 20 % 10 == 0 |
# 🐛 Bug Fix ## Preserve RL evaluation cadence - Keep rollout evaluation limited to configured interval and epoch boundaries - Retain final-step weight publication without forcing final-step evaluation - Document the optional final-trigger semantics of num_rollout - Align final-step evaluation comments across RL training modes --- # ✅ Tests ## Guard final-step behavior - Cover a non-periodic final rollout that must not trigger evaluation - Verify Hybrid publication still covers every actual evaluation boundary
|
✅ Acceptance passed
|
There was a problem hiding this comment.
Pull request overview
This PR optimizes Hybrid-async (text) training by reducing unnecessary reference/KL work when kl_loss_coef == 0, adding interval-based Hybrid weight publication, and hardening rollout pause/recover/resume coordination so failures don’t silently leave the system in an inconsistent state.
Changes:
- Auto-disable
--use-kl-losswhen--kl-loss-coef == 0and drop unusedreferenceresources. - Introduce Hybrid weight publication interval logic and align publication with eval/epoch boundaries and final-step forcing.
- Resolve RL
num_rollout_per_epoch/num_rollouton the controller before services are created; add focused regression tests for sizing + pause/resume behaviors.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
relax/utils/arguments.py |
Normalize zero-KL settings early and drop unused reference resources. |
relax/utils/misc.py |
Extend periodic-trigger helper to optionally force the final rollout. |
relax/engine/rollout/bootstrap.py |
New controller-side RL rollout sizing helper. |
relax/core/controller.py |
Resolve RL rollout sizing before service creation (incl. fully-async parallel creation). |
relax/distributed/ray/placement_group.py |
Avoid querying epoch sizing for non-global datasets; respect controller pre-resolution. |
relax/components/rollout.py |
Harden pause/prepare handshake for weight updates. |
relax/backends/megatron/actor.py |
Add interval-based Hybrid publication, resume retries, and synchronized failure handling. |
relax/engine/sft/bootstrap.py |
Update docstring to reflect RL sizing location. |
scripts/training/text/run-qwen3-0.6B-2xgpu-hybrid-async.sh |
Add Task 22 2-GPU Hybrid-async recipe with variants. |
tests/utils/conftest.py |
Centralize arguments_module fixture. |
tests/utils/test_arguments_zero_kl.py |
New tests for zero-KL normalization and reference resource dropping. |
tests/utils/test_arguments_opd_teacher_colocate.py |
Ensure zero-KL normalization happens before reference checkpoint validation. |
tests/engine/rollout/test_bootstrap.py |
New tests for RL rollout sizing resolution behavior. |
tests/distributed/ray/test_rollout_manager_bootstrap.py |
Ensure non-global datasets don’t query epoch length during RolloutManager bootstrap. |
tests/core/test_controller_rollout_bootstrap.py |
Ensure controller resolves epoch sizing before actor/rollout services are created. |
tests/components/test_rollout_weight_update_coordination.py |
Add tests for eval boundary logic and rollback on weight-update prepare failure. |
tests/backends/megatron/test_hybrid_weight_publication.py |
New tests for interval publishing + resume/retry + failure synchronization. |
tests/backends/megatron/test_actor_http_timeout.py |
Adjust tests to reflect new resume collective behavior and cleanup ordering. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Restore health monitoring when weight-update preparation fails after pause succeeds, and cover the rollback with a regression assertion. The separate handling for an uncertain pause result remains aligned with Michael-Salon's recommendation: only a confirmed pause with failed resume escalates to a synchronized job failure; a pause that is proven not to have been sent keeps the degradation path.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (2)
relax/engine/rollout/bootstrap.py:44
resolve_rl_num_rolloutrelies onassertfor user/config validation (dataset size vs batch size, and resolvednum_rolloutpositivity). Asserts can be disabled withpython -O, which would allow invalid configs to slip through and later fail in less obvious ways. Prefer raisingValueError(orRuntimeError) for these validation checks.
dataset_size = ray.get(data_source.lengths.remote())
num_per_epoch = dataset_size // config.rollout_batch_size
assert num_per_epoch > 0, f"Dataset size {dataset_size} < rollout_batch_size {config.rollout_batch_size}"
config.num_rollout_per_epoch = num_per_epoch
if config.num_epoch is not None:
epoch_rollout = num_per_epoch * config.num_epoch
config.num_rollout = (
min(config.num_rollout, epoch_rollout) if config.num_rollout is not None else epoch_rollout
)
assert config.num_rollout is not None and config.num_rollout > 0
relax/core/controller.py:418
resolve_rl_num_rollout()already resolvesnum_rollout_per_epochand may clampnum_rolloutwithmin(num_rollout, num_epoch * num_rollout_per_epoch). However,Controller.register_all_serve()still calls_maybe_resolve_num_rollout(roles_to_create)afterwards, which re-queriesdata_source.lengthsand overwritesconfig.num_rolloutasnum_epoch * num_rollout_per_epochunconditionally (dropping any user-provided--num-rolloutcap) and adds an extra Ray RPC. Consider either removing that second resolution for RL oncenum_rollout_per_epochis set, or updating_maybe_resolve_num_rolloutto preserve the existing min/clamp behavior.
data_source = ray.remote(num_cpus=1)(data_source_cls).remote(self.config)
# Actor and Rollout services are created in parallel for
# fully-async training, so resolve epoch boundaries on the
# controller before either service receives its config.
resolve_rl_num_rollout(self.config, data_source)
Copilot Review 处理说明1. pause 结果不确定时 resume 失败本次暂不采用 Copilot 提出的“所有不确定状态都 fail-closed”方案。当前实现遵循 Michael-Salon review 的建议:
Copilot 提出的风险已记录。若后续改用更保守的 fail-closed 策略,需要单独调整这一协议边界。 2. prepare 失败时未恢复 health monitoring该问题已修复,提交为 当
同时新增了回归断言,验证 health monitoring 必须恢复。 |
|
以下几个问题建议修改:
|
|
@Michael-Salon 最新 4 项意见已在 评审问题处理
v9 完整复测在基于
证据:Task 22 v9 fork release;完整报告。
验证
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 28 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (2)
relax/backends/megatron/actor.py:1870
- PR description states long-running recovery should keep the upstream “no fixed timeout” semantics, but /recover_rollout_engines is now called with
timeout=self.args.rollout_engine_init_timeout. If the intent is to introduce a finite recovery timeout (as tests suggest), the PR description should be updated; otherwise, remove the fixed timeout here to match the documented behavior.
response = requests.get(
f"{rollout_serve_url}/recover_rollout_engines",
timeout=self.args.rollout_engine_init_timeout,
)
relax/backends/megatron/actor.py:619
- PR description says long-running /evaluate should keep the upstream “no fixed timeout” semantics, but this call now uses
timeout=self.args.rollout_http_timeout, which can prematurely abort legitimately slow evaluations and contradicts the stated contract. Please either (a) update the PR description/any docs to match the new finite-timeout behavior, or (b) drop the fixed timeout here (and adjust tests accordingly).
This issue also appears on line 1867 of the same file.
response = requests.get(
f"{rollout_serve_url}/evaluate",
params={"train_step": rollout_id},
timeout=self.args.rollout_http_timeout,
)
|
关于 Copilot 最新 review 中两条 suppressed timeout 提示:已直接更新 PR 描述,使文档与
|
|
已复核当前提交 29a6a9b。上一轮提出的 checkpoint 回退、pause late-arrival、interval-two old log-prob 正确性以及短数据集兼容性问题均已修复,并有对应回归测试覆盖;新增测试文件命名也符合 test_*.py 约定。本轮未发现新的阻塞问题,LGTM |
What
本 PR 完成贡献者计划 Task 22「Hybrid-async 纯文本性能」,并补齐最新评审提出的配置初始化与 Rollout 恢复问题:
kl_loss_coef == 0时自动关闭无效的 KL/reference forward 及对应资源;--update-weights-interval 2每两次 Actor update 发布一次 Rollout 权重;num_rollout_per_epoch在所有 service 创建前解析并注入 Actor;eval_interval触发发布;rollout_http_timeout;recover_rollout_engines使用面向引擎重建的较长rollout_engine_init_timeout。这些控制面 HTTP 调用均采用有限超时,不再保留上游无固定 timeout 的语义;Closes #124.
Why
Hybrid-async 权重发布包含 Rollout pause、在途请求收敛、权重传输和 resume 的固定成本。小模型单步训练较短时,逐 step 发布容易形成流水空泡。interval-two 让两个 Actor update 共同摊销一次发布开销,代价是 Rollout 最多额外使用一次 Actor update 之前的权重。
kl_loss_coef == 0时 KL 项严格为零,自动去掉 reference forward 不改变标量目标,但能避免无效计算和资源初始化。Scope And Compatibility
--update-weights-interval,无新增或重命名 CLI/API;eval_interval、正常 interval 和最后一步发布;TASK22_VARIANT=zero_kl UPDATE_WEIGHTS_INTERVAL=1可关闭 interval-two 优化;0bc99af8中已经存在,fully-async 锁流程可追溯至初始提交eb15c45,不纳入本 PR 的通用分布式控制面重构。Experiment
90d9adb2(合入main@d52cd0a并完成语义化冲突解析);de5fd69e(benchmark 源码/数据/测试移至个人 fork release,测试按职责拆分);946a0dc6aeaed3eacc4e763c024bf8d8c898b9c4;本次复现实验提交:6a70677ddea0416f76b026a7669618a1f7eca27b;6d2d71b03a453a958686417428e0eaf1789cfa91;baseline传入原 zero-KL/reference 参数,由代码自动关闭 reference;zero_kl显式不传这些参数。两组是配置等价性和运行噪声对照,主验收对比为optimized相对zero_kl。Result
-1.50%,属于噪声对照;zero_kl:响应吞吐+14.86%,E2E 延迟-11.29%;+14.52% / +13.77% / +16.33%;+13.14%,E2E 延迟-8.33%;验收结论:通过,三次
optimized / zero_kl配对结果均超过 5% 目标。Memory Explanation
optimized Actor 峰值显存增加 7194 MiB。日志显示发布后 allocated/reserved 均约为 10.07/14.91 GiB;interval-two 跳过的 step 不执行
print_memory(..., clear_before_print=True),下次发布前 reserved 从约 37.5 GiB 增至约 44.6 GiB。增量是可回收的 PyTorch allocator cache 高水位,不是 live tensor、模型或 batch 增大;下次发布后回落到约 14.91 GiB。Evidence
3ab86aea9b12dbc9015efb52bec07d5b09bab3d77b43a7a3e568aff6733a47ef;ea1c0db8674b80780bc44a0c4bdab172bb8abcc4595f813aad37e39544f0b798;Testing
22 passed, 4 skipped(缺少可选 Megatron 依赖时显式 skip);arguments 扩展测试:18 passed;HTTP timeout/resume fixtures:30 passed;compileall、shell 语法和git diff --check:通过;main不包含benchmarks/、tests/benchmarks/、.gitignore或 pre-commit 配置改动;Correctness Boundary
这是短周期性能实验。大部分 step 的 DAPO reward 没有组内方差,不能据此证明 interval-two 与 interval-one 的长期收敛等价。结论不外推到 Qwen3-4B/8 GPU。