Skip to content

feat(multi-gpu): integrate single-node off-policy and PPO data parallelism - #982

Merged
TATP-233 merged 13 commits into
mainfrom
feat/issue-964-offpolicy-multi-gpu-dp
Aug 15, 2026
Merged

feat(multi-gpu): integrate single-node off-policy and PPO data parallelism#982
TATP-233 merged 13 commits into
mainfrom
feat/issue-964-offpolicy-multi-gpu-dp

Conversation

@TATP-233

@TATP-233 TATP-233 commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

This is the final umbrella integration PR for #964. The implementation work was split across reviewed child PRs and accumulated on feat/issue-964-offpolicy-multi-gpu-dp; this PR does not introduce a new implementation scope.

Final off-policy contract

The original roadmap proposed periodic parameter averaging through training.dp_sync_interval. The final implementation and #964 have been explicitly revised:

  • startup model and optimizer state are broadcast across ranks
  • before every actual optimizer step, stable flat-gradient buffers are all-reduced and averaged
  • each rank then performs its local optimizer step; there is no training.dp_sync_interval
  • replay, environments, exploration seeds, and collectors remain rank-local
  • collectors still own no actor and initialize no CUDA
  • omitting training.devices preserves the existing single-GPU path

This uses runner-owned collectives rather than a PyTorch DDP wrapper and does not introduce a second collector/learner lifecycle.

Included work

Validation

The final PR head tree is identical to the tree validated by:

  • make test-all — 1657 passed, 24 skipped, 1 xfailed; coverage 72%; benchmark import smoke passed
  • all three README commands compose through Hydra --cfg job, resolving the documented 2/4/8-device lists; the hardware validation boundary remains the 2-GPU platform stated below
  • single-core (taskset -c 0) IPC regression — 10 passed, 1 slow test deselected; sparse-statistics worker stress — 10/10 passed
  • the GitHub Actions test job now has a 15-minute hard timeout
  • FastSAC and FlashSAC two-GPU lifecycle, synchronization, checkpoint, and failure-propagation smokes
  • FastSAC and FlashSAC graph-enabled two-GPU smokes, including a collective for every actual optimizer update
  • RSL-RL PPO two-GPU slow smokes for Go2 joystick flat and G1 motion tracking
  • direct Go2 train-to-playback regression: video creation followed by natural exit after Done., without a second NCCL process group

The branch is 13 commits ahead of main; a merge-tree check reports no conflicts.

Throughput

Off-policy validation used 2 × RTX 6000D and Threadripper 9980X with the production FastSAC configuration:

GPUs Aggregate env steps/s Scaling Final reward
1 64,242 1.00× 7.96
2 110,804 1.72× 7.93

PPO results are weak-scaling measurements with 1024 environments per rank, rollout length 24, and 10 steady-state iterations after discarding 2 warmup iterations:

Task GPUs Mean samples/s Scaling
Go2 joystick flat 1 134,258.5 1.000×
Go2 joystick flat 2 218,351.9 1.626×
G1 motion tracking 1 67,999.5 1.000×
G1 motion tracking 2 117,617.4 1.730×

No PPO model-quality claim is made; acceptance is semantic correctness, lifecycle completion, and throughput reporting.

Support boundary

  • validated on Linux, one node, MuJoCo, and 2 × RTX 6000D
  • off-policy support: FastSAC and FlashSAC
  • PPO support: RSL-RL Go2 joystick flat and G1 motion tracking examples
  • TD3, Motrix multi-GPU, cross-rank replay, and multi-node execution remain out of scope
  • PPO observation normalization, curriculum, and episode statistics remain rank-local; rank 0 checkpoint/log state is authoritative
  • NCCL_P2P_DISABLE=1 and NCCL_SHM_DISABLE=1 are defaults for the validated platform; explicit user environment values take precedence

Closes #964
Related: #978

TATP-233 and others added 10 commits August 14, 2026 05:19
* feat(offpolicy): add multi-GPU data-parallel rank topology

Add training.devices config surface and rank topology for off-policy
data-parallel training (issue #965, child of roadmap #964). Rank 0 owns
the ExperimentTracker and canonical checkpoints and supervises ranks
1..N-1 via DpRankSupervisor; each spawned rank re-runs
scripts/train_offpolicy.py with UNILAB_DP_* env, its own cuda device
(cuda:devices[rank]) and seed+rank. Parameter sync between ranks is out
of scope for this stage. null/[]/[0] degenerate to the exact current
single-device behavior.

* fix(offpolicy): wait for sibling ranks on normal rank-0 exit

DpRankSupervisor previously terminated still-running rank subprocesses as
soon as rank 0 finished, so sibling ranks never completed their own runs
(exit 0 masked the teardown). Give ranks a grace window to finish; a rank
that exceeds the grace or exits non-zero still fails the whole run.

* style(ipc): annotate supervisor __exit__ as Literal[False]

* style(tests): apply ruff format to dp_launcher tests
* feat(offpolicy): partition collector CPU cores per data-parallel rank

Each rank's collector owns one contiguous CPU block (cpu_count // world_size,
rank i -> [i*size, (i+1)*size); remainder CPUs keep default OS scheduling),
routed through EnvCfg.cpu_ids into the MuJoCo BatchEnvPool only for the
collector env — the learner-side num_envs=1 probe envs keep the base override
untouched. training.dp_collector_cpu_ids allows an explicit per-rank override.
Single-rank runs keep bit-identical behavior (no cpu_ids, host-sized thread
budget); multi-rank runs resolve the torch thread budget against the rank's
CPU share.

Closes #966

* fix(offpolicy): derive DP world size from training.devices for rank 0

Rank 0 carries no UNILAB_DP_* environment (the supervisor only sets it for
spawned ranks), so its collector CPU partition and thread budget silently
fell back to the single-rank path and rank 0's BatchEnvPool workers stayed
unpinned. Resolve world_size from training.devices instead; rank still
comes from the env (0 for rank 0). Verified on a 2-GPU host: rank0 pool
workers pinned to CPUs 0-63, rank1 to 64-127.

* style: apply ruff format from make check
* feat(offpolicy): 多 learner 周期性参数平均同步(torch.distributed)

- 新增 unilab.ipc.dp_sync.DpParameterSync:FileStore rendezvous 的
  process group 封装,broadcast_from_rank0 / allreduce_mean 均为原地
  集合通信,key 序首次固定,close 幂等。
- FastSACLearner.dp_sync_tensors() 返回 actor/qnet/qnet_target
  state_dict 的活引用加 log_alpha(不含 optimizer state)。
- DoubleBufferOffPolicyRunner:collector 启动前做 init broadcast;
  每个 learner iteration 的 update 边界(finish_update 之后、
  log_step 之前)按 dp_sync_interval 做参数 all-reduce 平均,
  耗时以 dp_sync_time 计入 iter_metrics;manifest 记录 dp_sync。
- build_runner 冷路径装配:training.dp_sync_interval(默认 8,<1 即
  ValueError),world_size>1 时构造 DpParameterSync,rendezvous 锚定
  每个 run 唯一的 log 根目录(rank>0 走 UNILAB_DP_LOG_DIR)。
- world_size=1 路径不建 process group,逐位等价。
- 测试:gloo 双进程 broadcast/allreduce/key 序/原地语义;runner 集成
  (fake learner + fake dp_sync);build_runner 装配与非法值校验;
  NCCL 双卡冒烟(slow + cuda>=2 skipif)。

Validation: pytest tests/ipc tests/algos 全绿;全量非 slow 单测
1582 passed;make type 0 error。

Fixes #967

* fix(ipc): pin rank CUDA device and force TCP loopback for NCCL dp sync

Two host-level NCCL failures surfaced in the 2-GPU acceptance run:
- ranks other than 0 kept the default current device (cuda:0) while their
  learner tensors lived on cuda:1, hanging the first collective (NCCL
  watchdog timeout after 120s);
- with P2P disabled the SHM transport crashed with CUDA illegal memory
  access during multi-tensor all_reduce (reproduced with repo-free code).

DpParameterSync.start() now pins torch.cuda.set_device to the rank device
and defaults NCCL_P2P_DISABLE/NCCL_SHM_DISABLE=1 (env override wins), so
collectives use the TCP loopback transport. Verified: NCCL unit smoke
passes in 3s; a 30-iteration K=1 two-rank run leaves rank0/rank1
checkpoints bitwise identical across all 77 synced tensors.

* style: apply ruff format from make check
- benchmark/rl/benchmark_offpolicy_dp_scaling.py: subprocess 跑真实
  train_offpolicy.py,对比 N=1 与 N-way DP;稳态吞吐取 tfevents
  perf/steps_per_sec 后 50% 采样点均值,聚合为各 rank 求和;输出
  scaling ratio 与 1.7x 阈值 verdict;run 失败退出码非零
- tests/benchmark/test_offpolicy_dp_scaling_benchmark.py: 假 run 目录
  (run_summary.json + 假 tfevents) 覆盖稳态均值、N=2 聚合、rank1
  缺失报错、verdict 判定,不起真实训练
- docs: SAC 页补多卡数据并行用法与限制(单节点 / sac only / NCCL
  TCP loopback 原因);支持矩阵结构无法表达多卡,未改生成器
FlashSACLearner 实现 dp_sync_tensors()(actor/critic/target_critic/
temperature state_dict 活引用),build_runner 的 flashsac 分支接通
dp_sync / dp_sync_interval / collector_cpu_ids,与 sac 共用同一套
runner 机制;单卡路径 dp_sync=None 保持不变。

Refs #969
* fix(offpolicy): stabilize multi-GPU graphs and logging

Warm up and capture NCCL gradient collectives safely for FastSAC and FlashSAC CUDA graphs. Consolidate rank-zero terminal ownership, fixed-rate rolling statistics, aggregate throughput reporting, and synchronous collector telemetry.

* fix(offpolicy): clean up multi-GPU interrupts

* feat(ppo): add single-node RSL-RL multi-GPU training (#980)
@TATP-233
TATP-233 merged commit 21dbe46 into main Aug 15, 2026
7 checks passed
@TATP-233
TATP-233 deleted the feat/issue-964-offpolicy-multi-gpu-dp branch August 15, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

roadmap(offpolicy): 多卡数据并行(N learner × N collector × N GPU,逐 optimizer step 梯度平均)

1 participant