本仓库保存 FlashSAC 的 full-objective compile 优化、补丁、测试和可复现实验脚本。优化目标是在不改变算法更新次数和数学定义的前提下,减少 GPU kernel launch、CPU/GPU 同步、无效梯度和 metrics 拷贝开销。
详细原理、完整路径定义和全部实验数据见 FLASH_SAC_OPTIMIZATION.md。
物理训练结果只适用于下面这组版本。不要把 unilab-rl、unisim-core
或 MuJoCo 升级后,仍然把新结果和本仓库的结果直接比较:
| 组件 | 固定版本 | 备注 |
|---|---|---|
| UniLab | 1.3.2 |
Git 82fa794a88ee9466d8588e7c4a3a00dfeadd451f |
unilab-rl 兼容依赖 |
1.4.0 |
UniLab owner 环境中的精确 wheel 版本 |
| FlashSAC 优化源码 | Git abb3d82 |
本仓库 src/uni_rl;基准脚本优先使用它 |
unisim-core |
1.7.8 |
精确 pin,不使用 >= |
| PyTorch | 2.8.0+cu128 |
CUDA 12.8 wheel |
| MuJoCo | 3.11.0 |
与 UniLab MuJoCo extra 匹配 |
| GPU | NVIDIA GeForce RTX 4090 | 结果中的硬件 |
REPRODUCE_VERSIONS.env 保存了同一组机器可读的值。这里的
unilab-rl==1.4.0 是 UniLab 的兼容依赖;运行优化基准时,脚本会把本仓库
的 src/uni_rl 放在 PYTHONPATH 最前面,因此真正执行的是表中指定的
FlashSAC 优化源码快照。运行前可以检查:
本仓库 pyproject.toml 中的 unilab-rl 版本号 1.2.1 是这份源码快照的
历史发行元数据,不是物理训练使用的 UniLab owner wheel。不要把这两个版本
混为一谈;以 PYTHONPATH 指向的源码和下面的 package_versions 为准。
cd /path/to/UniLab
PYTHONPATH=/path/to/UniLab-FlashSAC-Optmization/src \
uv run --locked --no-sync -- python -c \
'import importlib.metadata as m, torch, mujoco, unisim, unilab, uni_rl; \
print("unilab", m.version("unilab")); \
print("unilab-rl", m.version("unilab-rl")); \
print("unisim-core", m.version("unisim-core")); \
print("torch", torch.__version__); \
print("mujoco", mujoco.__version__); \
print("unilab", unilab.__file__); \
print("uni_rl", uni_rl.__file__); \
print("unisim", unisim.__file__)'输出应分别为 1.3.2、1.4.0、1.7.8、2.8.0+cu128 和
3.11.0。如果 uni_rl.__file__ 没有指向本仓库的
src/uni_rl/__init__.py,说明没有使用优化源码。
- 将 actor/critic 的完整 objective 放进
torch.compile,扩大 Inductor 的融合范围。 - actor 更新时冻结 critic 参数,只保留 action 到 critic 输出的梯度路径。
- finite guard 留在 GPU,避免每次 optimizer step 都同步到 CPU。
- metrics 延迟到一轮 learner update 末尾统一 D2H。
- 使用固定 metrics buffer,避免 Inductor CUDA Graph 或手工 CUDA Graph replay 覆盖返回值。
- 正确处理 Inductor 内部 CUDA Graph 与外层手工 CUDA Graph:纯 compile 路径允许 Inductor Graph,手工 Graph 路径关闭嵌套 capture。
推荐默认配置:
algo_params:
use_compile: true
compile_full_objectives: true
use_cuda_graph_critic: false
use_cuda_graph_actor: false手工 CUDA Graph 只建议用于 batch 和输入 shape 固定、追求极限速度且能承担更复杂 graph 生命周期管理的场景。
下面的配置都作用于 algo.algo_params。可以临时在命令行覆盖,也可以把
相同字段写入 UniLab 的 src/unilab/conf/flashsac/config.yaml。每次修改后都
应保留输出 JSON,便于比较同一统计口径下的指标。
这是本报告的优化主路径:编译完整 actor/critic objective,不启用外层手工 CUDA Graph。
algo_params:
use_compile: true
compile_full_objectives: true
use_cuda_graph_critic: false
use_cuda_graph_actor: false命令行写法:
algo.algo_params.use_compile=true \
algo.algo_params.compile_full_objectives=true \
algo.algo_params.use_cuda_graph_critic=false \
algo.algo_params.use_cuda_graph_actor=false用于判断“扩大 compile 边界”本身带来的收益:
algo_params:
use_compile: true
compile_full_objectives: false
use_cuda_graph_critic: false
use_cuda_graph_actor: false用于排查编译器或 CUDA Graph 问题。它仍保留本仓库的 deferred metrics 等 安全优化,但不使用 Inductor:
algo_params:
use_compile: false
compile_full_objectives: false
use_cuda_graph_critic: false
use_cuda_graph_actor: false只有输入 shape、optimizer state 和 staging buffer 全部稳定时才尝试:
algo_params:
use_compile: true
compile_full_objectives: true
use_cuda_graph_critic: true
use_cuda_graph_actor: true手工 Graph 由 learner 负责 capture/replay;本仓库会关闭 Inductor 内部的 CUDA Graph Trees,避免嵌套 capture。它适合单独做峰值实验,不是报告中真实 MuJoCo 结果的默认路径。
如果已经把上游 Issue #94 的独立 whole-cycle 开关合入 unilab_rl,推荐使用
下面的等价配置关闭外层整轮 Graph,同时保留 Inductor;它需要对应的新核心
版本,不能直接用于本仓库的旧 API:
algo_params:
use_compile: true
compile_full_objectives: true
use_whole_cycle_cuda_graph: false两种 update 口径均使用 policy_frequency=2:
- 1 actor / 2 critic:每 iter 有 2 次 critic、1 次 actor、1 次 temperature update。
- 4 actor / 8 critic:每 iter 有 8 次 critic、4 次 actor、4 次 temperature update。
RTX 4090、G1 Walk Flat、256 env、batch 256、1000 iter,统计最后 500 iter。单位为 ms:
| update 口径 | 路径 | learner mean | median | p90 | p95 | learner 降幅 | iter mean |
|---|---|---|---|---|---|---|---|
| 1 actor / 2 critic | deferred eager | 10.118 | 9.965 | 10.760 | 11.092 | — | 11.009 |
| 1 actor / 2 critic | full-objective compile | 4.323 | 4.397 | 4.874 | 5.088 | 57.3% | 5.341 |
| 4 actor / 8 critic | deferred eager | 36.885 | 36.183 | 39.010 | 40.171 | — | 37.760 |
| 4 actor / 8 critic | full-objective compile | 13.304 | 13.145 | 14.344 | 14.888 | 63.9% | 14.235 |
这里的 learner 是一个 iter 内全部 update 的总耗时,不是单次 critic 或 actor update。iter 是完整 runner iteration 墙钟时间。collector 与 learner 采用 double buffer,会发生重叠,因此 iter != learner + collector。
update 数量扩大 4 倍时,真实训练的优化后 learner mean 从 4.323 ms 增加到 13.304 ms,只增加 3.08 倍。原因是每 iter 固定开销只支付一次,并且 metrics D2H 和最终 CUDA synchronize 也只做一次;详细定量分析见优化报告。
假设目录为:
/path/to/UniLab
/path/to/UniLab-FlashSAC-Optmization
UniLab 环境需能导入 torch、unisim、MuJoCo 和 TensorBoard。先检查:
cd /path/to/UniLab
uv run --project /path/to/UniLab --locked --no-sync -- python -c \
'import torch, unisim; print(torch.__version__); print(torch.cuda.get_device_name(0)); print(unisim.__file__)'不要直接执行 uv run /path/to/UniLab-FlashSAC-Optmization/...。这种写法可能让 uv 选择优化仓库自己的 .venv,导致 ModuleNotFoundError: unisim。下面所有命令都显式使用 UniLab 项目环境。
下面命令真正调用 UniLab 的 train_flashsac.py 并创建 MuJoCo 环境,同时保留生产 Rich 实时面板。默认训练 1000 iter,统计最后 500 iter:
uv run --project /path/to/UniLab --locked --no-sync -- python \
/path/to/UniLab-FlashSAC-Optmization/experiments/benchmark_flashsac_training.py \
--unilab-root /path/to/UniLab \
--backend mujoco --iterations 1000 --summary-last 500 \
--num-envs 256 --batch-size 256 --replay-buffer-n 32 \
--learning-starts 8 \
--updates-per-step 2 --policy-frequency 2 \
--compile \
--output /path/to/UniLab-FlashSAC-Optmization/results/physical_u2_compile.json四组对照只需修改以下参数:
| 实验 | 参数 |
|---|---|
| 1 actor / 2 critic,优化后 | --updates-per-step 2 --policy-frequency 2 --compile |
| 1 actor / 2 critic,deferred eager | --updates-per-step 2 --policy-frequency 2 --no-compile |
| 4 actor / 8 critic,优化后 | --updates-per-step 8 --policy-frequency 2 --compile |
| 4 actor / 8 critic,deferred eager | --updates-per-step 8 --policy-frequency 2 --no-compile |
每组使用不同的 --output 文件名。JSON 中:
rows_all:全部共同 timing 样本;rows:最后 500 个参与统计的样本;learner_train_ms、collector_cycle_ms、iter_ms:mean、median、p90、p95;training_config:每 iter 的 critic、actor、temperature update 数量;device.package_versions:实际启动环境发现的unilab、unilab-rl、unisim-core和mujoco版本;device.flashsac_source:实际使用的优化源码路径。
真实训练必须同时查看三组时间,每组报告 mean、median、p90、p95:
| JSON 字段 | 含义 | 推荐用途 |
|---|---|---|
learner_train_ms |
一个 iter 内全部 critic/actor/temperature update 的 learner 总耗时 | 判断算法更新本身是否变快 |
collector_cycle_ms |
collector 一个周期的耗时,包含等待 learner inference 的影响 | 判断采集端是否被 learner 拖慢 |
iter_ms |
完整 runner iteration 墙钟时间 | 最终端到端指标 |
本报告的 4090 基线为:
| 口径 | 路径 | learner mean / p95 | iter mean / p95 |
|---|---|---|---|
| 1 actor / 2 critic | deferred eager | 10.118 / 11.092 ms | 11.009 / 12.032 ms |
| 1 actor / 2 critic | full-objective compile | 4.323 / 5.088 ms | 5.341 / 6.213 ms |
| 4 actor / 8 critic | deferred eager | 36.885 / 40.171 ms | 37.760 / 41.041 ms |
| 4 actor / 8 critic | full-objective compile | 13.304 / 14.888 ms | 14.235 / 15.886 ms |
复现实验时,至少应满足:
- JSON 中
summary_last=500,且iterations=1000; training_config明确记录 critic、actor、temperature 的 update 数;- 三组 timing 字段都存在,并同时给出 mean、median、p90、p95;
iter_ms是判断端到端性能的主指标,不能只报告单次 update;- 数值检查和 replay 稳定性检查均返回
passed=true; - 在本固定环境下,4 actor / 8 critic 的 full-objective compile 应保持约
14.235 ms/itermean、15.886 ms/iterp95 的量级,明显低于 20 ms。
uv run --project /path/to/UniLab --locked --no-sync -- python experiments/validate_numerics.py
uv run --project /path/to/UniLab --locked --no-sync -- python experiments/validate_replay_stability.py