Skip to content

Commit e9baefa

Browse files
committed
perf(flashsac): add opt-in DP CUDA graphs and BF16 gradient wire
1 parent 03b7535 commit e9baefa

17 files changed

Lines changed: 578 additions & 25 deletions

File tree

‎AGENTS.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@ architecture changes.
2929

3030
## Architecture contracts
3131

32+
- FlashSAC DP whole-cycle CUDA graphs require explicit
33+
`algo.algo_params.dp_whole_cycle_cuda_graph=true` and synchronizer lifecycle
34+
hooks. Capture failure must raise on DP; rank-local eager fallback can hang
35+
collectives. Release graphs before closing the process group. On the compiled
36+
CUDA path without GradScaler, loss validity
37+
travels with gradients so nonfinite losses with finite derivatives skip the
38+
same optimizer and target update on every rank.
39+
- `training.dp_gradient_wire_dtype` defaults to `float32`. Optional `bfloat16`
40+
compresses FP32 groups with at least 1024 elements, restores FP32 before
41+
averaging, and changes numerical precision independently of AMP. Do not claim
42+
convergence equivalence from short throughput runs. Distributed communication
43+
remains in `rl/runtime/distributed`, never downstream task packages.
44+
- Multi-GPU MPS uses one named daemon per selected physical GPU. Select each
45+
rank's pipe and log before CUDA initialization; inherited rank-zero paths must
46+
be replaced in spawned ranks. Keep explicit GPU selection in launch docs.
47+
3248
- UniLab provides environments and adapters, and now also owns the RL
3349
algorithm and training-execution stack in `unilab.rl` (merged from the
3450
unilab_rl repo @ 3654e4b). Its layers: `contracts` (env injection and

‎README.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ and [support matrix](https://unilabsim.github.io/UniLab-doc/en/5-reference/5-sup
111111

112112
## Ecosystem
113113

114+
For multi-GPU FlashSAC, select the GPU UUID list with `CUDA_VISIBLE_DEVICES`;
115+
the native launcher starts one rank per visible GPU. Keep `algo.num_envs` and
116+
`algo.batch_size` fixed per rank. Opt into whole-cycle DP graphs with
117+
`+algo.algo_params.dp_whole_cycle_cuda_graph=true`; communication remains FP32
118+
unless `training.dp_gradient_wire_dtype=bfloat16` is explicitly selected.
119+
See [execution and precision contracts](docs/flashsac_dp.md) and the
120+
[FlashMimic launch instructions](https://github.com/Motphys/FlashMimic#train)
121+
for per-GPU MPS services and the live training panel.
122+
114123
UniLab is designed to be a shared task and training surface for robot-specific
115124
repositories. They can ship robot recipes independently while consuming the
116125
same task, backend, and RL contracts. Current downstream examples:

‎README_zh.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ uv run --extra genesis eval --algo flashsac --task g1_motion_tracking --sim gene
104104

105105
## 生态
106106

107+
多卡 FlashSAC 通过 `CUDA_VISIBLE_DEVICES` 指定 GPU UUID 列表,原生 launcher
108+
为每张可见卡启动一个 rank。`algo.num_envs` 和 `algo.batch_size` 均保持每 rank
109+
固定。使用 `+algo.algo_params.dp_whole_cycle_cuda_graph=true` 显式启用 DP 整周期
110+
CUDA graph;通信默认 FP32,可通过 `training.dp_gradient_wire_dtype=bfloat16`
111+
显式选择有损 BF16。详见[执行与精度约定](docs/flashsac_dp.md)和
112+
[FlashMimic 启动说明](https://github.com/Motphys/FlashMimic#train),包含逐卡 MPS
113+
服务管理和训练面板。
114+
107115
UniLab 被设计为机器人专属仓库共享的任务与训练界面。下游仓库可以独立发布机器人
108116
recipe,同时消费同一套 task、backend 和 RL contract。目前的下游示例:
109117

‎docs/flashsac_dp.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# FlashSAC data-parallel CUDA graphs and wire precision
2+
3+
## Scope and decision
4+
5+
This change ports the measured FlashMimic multi-GPU optimization to UniLab
6+
1.5.0 (`03b75353`) with UniSim 2.1.0 (`6994cd1`). The intended PR base is `main`.
7+
The requested scope is opt-in DP graph execution and optional BF16 gradient
8+
communication; the existing runner owns process lifecycle and synchronization.
9+
No new runner or backend protocol is introduced. Defaults remain FP32 and DP
10+
eager execution. The actor/temperature fusion experiment is excluded because
11+
it did not provide a stable throughput improvement.
12+
13+
The proposed contract is recorded in
14+
[ADR-0020](sphinx/source/adr/ADR-0020-flashsac-dp-graph-and-wire-precision.md).
15+
16+
## Execution contract
17+
18+
Set `+algo.algo_params.dp_whole_cycle_cuda_graph=true` to let FlashSAC capture
19+
the update cycle with NCCL reductions. The runner attaches synchronizer hooks
20+
around capture and before replay. Replacing callbacks, hooks, or checkpoint
21+
state invalidates the graph. A DP capture failure raises rather than allowing
22+
one rank to fall back to eager collectives. A loss-validity sentinel is packed
23+
with each optimizer's gradients, including losses whose derivatives are finite.
24+
On the compiled CUDA path without GradScaler, every rank therefore gates that
25+
optimizer and the critic target consistently. CPU, eager CUDA and FP16 scaler
26+
paths retain their existing finite-check behavior.
27+
28+
This is raw CUDA graph capture around compiled objective/backward segments and
29+
optimizer updates. It is not a claim that the entire learner was compiled into
30+
one `torch.compile(fullgraph=True)` graph.
31+
32+
`training.dp_gradient_wire_dtype=float32` preserves the default reduction.
33+
Explicit `bfloat16` packs FP32 groups of at least 1024 elements in BF16, reduces
34+
their SUM, restores gradients to FP32, then divides in FP32. Smaller groups and
35+
other source dtypes retain their original dtype. Parameters and optimizer states
36+
retain their existing dtype. Layout and precision must match across all ranks.
37+
This is a lossy numerical option, independent of AMP; long-run convergence
38+
equivalence has not been demonstrated.
39+
40+
## Evidence and acceptance
41+
42+
Historical experiments used UniLab `c71a7a20`, UniSim `26d17b4e`, FlashMimic
43+
`d3990125`, with env=2048, batch=2048 per rank and six updates per cycle. On the
44+
tested eight-GPU host, BF16 wire increased median throughput from 346875 to
45+
388227 steps/s (+11.9%). Relative gradient L2 error was approximately 0.3%.
46+
These are historical measurements, not benchmarks of the new upstream defaults.
47+
Learner time still grew with rank count; constant learner time was not achieved.
48+
49+
Acceptance covers default precision, BF16 threshold and mean semantics, stable
50+
buffer storage, graph ownership and failure behavior, rank-wide nonfinite-loss
51+
gates, and Linux multi-GPU capture/replay. README commands show explicit GPU
52+
selection and user-owned MPS. CPU mock tests do not establish NCCL graph support.
53+
Full repository and current-head remote CI gates remain required before merge.

‎docs/sphinx/source/adr/ADR-0000-index.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ orphan: true
3131
| [ADR-0017 FlashSAC Learner-Side Custom Actor Contract](ADR-0017-flashsac-learner-side-custom-actor-contract.md) | RL algorithm extension / checkpoint semantics | Proposed |
3232
| [ADR-0018 Collector Tensor Device Sole Placement Knob](ADR-0018-collector-tensor-device-sole-placement-knob.md) | Training config / off-policy placement | Proposed |
3333
| [ADR-0019 CUDA Learner Forbids CPU Carrier](ADR-0019-cuda-learner-forbids-cpu-carrier.md) | Training config / carrier pairing contract | Proposed |
34+
| [ADR-0020 FlashSAC DP Graph Ownership And Wire Precision](ADR-0020-flashsac-dp-graph-and-wire-precision.md) | RL algorithm / distributed runtime | Proposed |
3435
| [ADR-0231 Integration-only K=1 MJWarp Collector CUDA Graph](ADR-0231-k1-mjwarp-collector-cuda-graph-integration-only.md) | Collector CUDA graph / historical pre-registration | Rejected / no-go at M2 |
3536
| [ADR-0238 State-Update-Only CUDA Graph Boundary](ADR-0238-state-update-only-cuda-graph-boundary.md) | Manager CUDA graph / historical pre-registration | Rejected / no-go |
3637

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
orphan: true
3+
---
4+
5+
# ADR-0020 FlashSAC DP Graph Ownership And Wire Precision
6+
7+
- Status: Proposed
8+
- Date: 2026-10-22
9+
- Owners: UniLab RL runtime maintainers
10+
- Supersedes: None
11+
- Superseded by: None
12+
13+
## Context
14+
15+
FlashSAC disables whole-cycle capture when gradient synchronization is attached,
16+
although the distributed runner already provides graph lifecycle hooks. Multi-GPU
17+
profiling also shows that large gradient reductions dominate learner time on
18+
hosts without GPU peer access. Downstream FlashMimic should consume these runtime
19+
capabilities rather than patching algorithm or communication implementations.
20+
21+
## Decision
22+
23+
Keep the existing runner and process-group owner. Add an explicit FlashSAC DP
24+
whole-cycle capture opt-in and a separate runtime wire-precision option. Preserve
25+
FP32 communication and existing DP eager execution as defaults. This decision
26+
does not change physics backend contracts or establish BF16 convergence parity.
27+
28+
## Stable Contracts
29+
30+
- `algo.algo_params.dp_whole_cycle_cuda_graph=true` requires synchronizer hooks;
31+
changing hooks, gradient callbacks, or checkpoint state invalidates capture.
32+
- DP capture errors propagate. A rank must not independently fall back to eager
33+
collectives. Graphs are released before process-group destruction.
34+
- Compiled CUDA execution without GradScaler includes loss validity in gradient
35+
reduction, so all ranks skip the same invalid optimizer and critic target step.
36+
- `training.dp_gradient_wire_dtype` accepts `float32` (default) or `bfloat16`.
37+
BF16 affects only FP32 groups with at least 1024 elements. Restore FP32 gradients
38+
before division; preserve parameter/optimizer dtype and gradient storage.
39+
- Every rank uses the same layout and precision. The option is independent of AMP.
40+
- MPS pipes/logs are selected per rank before CUDA initialization. Explicit host
41+
daemon directories do not require access to another user's default runtime root.
42+
43+
## Alternatives Considered
44+
45+
- Keep graph execution disabled: retains avoidable learner launch overhead.
46+
- Enable BF16 by default: rejected because gradient reduction is lossy and
47+
long-run convergence equivalence has not been established.
48+
- Fuse actor and temperature reductions: excluded after no stable measured
49+
throughput improvement; it would also change the small-group precision boundary.
50+
- Put optimized runtime code in FlashMimic: rejected because UniLab owns DP.
51+
52+
## Consequences
53+
54+
Optional CUDA/NCCL execution requires real multi-rank validation in addition to
55+
CPU regression tests. Historical throughput evidence must identify its exact
56+
source pins and configuration; it is not a promise for newer backend defaults.
57+
Capturing a raw CUDA cycle does not imply one fully compiled learner FX graph.
58+
59+
## Evidence In Repo
60+
61+
- `src/unilab/rl/algos/flash_sac/learner.py`
62+
- `src/unilab/rl/runtime/distributed/sync.py`
63+
- `src/unilab/rl/runtime/offpolicy/double_buffer_runner.py`
64+
- `src/unilab/scripts/train_offpolicy.py`
65+
- `tests/rl/algos/test_flash_sac_dp_graph.py`
66+
- `tests/rl/runtime/test_dp_wire_precision.py`
67+
- `tests/algos/test_offpolicy_dp_sync.py`
68+
- `tests/training/test_cuda_mps_cli.py`
69+
70+
## Related Documents
71+
72+
- {doc}`ADR-0016-rl-reintegration-into-unilab-rl`
73+
- {doc}`ADR-0014-cuda-mps-cli-lifecycle-owner`

‎src/unilab/conf/flashsac/config.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ training:
9292
# process sharing and fails closed if the host daemon is not already valid.
9393
# Manage an explicit user-owned daemon with `uni-cumps`.
9494
cuda_process_sharing: null
95+
# Optional lossy gradient communication, independent of learner AMP.
96+
dp_gradient_wire_dtype: float32
9597
nan_guard:
9698
enabled: true
9799
buffer_size: 100

‎src/unilab/conf/sac/config.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ training:
7070
# process sharing and fails closed if the host daemon is not already valid.
7171
# Manage an explicit user-owned daemon with `uni-cumps`.
7272
cuda_process_sharing: null
73+
dp_gradient_wire_dtype: float32
7374
nan_guard:
7475
enabled: true
7576
buffer_size: 100

‎src/unilab/rl/algos/flash_sac/double_buffer.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def build_double_buffer_runner(
129129
"use_compile": cfg.algo.algo_params.use_compile,
130130
"compile_full_objectives": _algo_param(cfg.algo, "compile_full_objectives", None),
131131
"use_whole_cycle_cuda_graph": _algo_param(cfg.algo, "use_whole_cycle_cuda_graph", None),
132+
"dp_whole_cycle_cuda_graph": _algo_param(cfg.algo, "dp_whole_cycle_cuda_graph", False),
132133
"actor_normalize_parameters": bool(
133134
_algo_param(cfg.algo, "actor_normalize_parameters", True)
134135
),

0 commit comments

Comments
 (0)