Problem
在 UniLab 中,用户可以在 MuJoCo 或 Motrix 后端训练策略,然后在另一个后端进行 sim2sim(play)验证。然而,当前 play 脚本完全依赖当前 Hydra 合成的配置(即目标后端的 YAML)来创建环境,而同一个 task 的 mujoco.yaml 与 motrix.yaml 是独立维护的,经常出现不一致:
algo.obs_groups 结构不同(如 mujoco 用 actor,motrix 用 policy/critic)(?存疑,验证是否真的存在这个问题)
env.action_scale 不同(如 0.25 vs 0.5)
env.reward 权重、base_height_target、max_tilt_deg 不同
env.commands、domain_rand、gait_phase_init_mode 等关键参数不同
当这些差异存在时,目标后端创建出的环境 obs/action 空间与训练时不匹配,导致策略网络输入维度错误或语义偏移,最终表现为崩溃或行为异常。由于 checkpoint(.pt)仅保存权重,不携带训练时的配置,用户只能在运行时才能发现不一致,调试成本极高。
Root Cause
- Checkpoint 不自包含:rsl-rl/APPO/offpolicy/MLX PPO 的
.pt 仅保存 actor_state_dict 等权重,没有保存训练时的 env/algo 配置。
- Play 配置来源单一:
play_rsl_rl() 等入口使用当前 Hydra cfg 调用 BackendAdapter.build_play_env_cfg_override(),无法感知训练时的配置。
- YAML 无强制一致性:同一 task 的 backend-specific YAML 独立编辑,没有分层机制(如
base.yaml)来锁定跨后端必须一致的字段。
Proposed Solution(Sidecar + Diff-Merge)
Phase 1:最小侵入的 Sidecar 方案
利用训练时已存在的 run_config.json sidecar(由 ExperimentTracker.start() 写入,包含完整 resolved full_cfg),实现"训练配置恢复 + 目标后端覆盖"的合并机制。
新增模块 src/unilab/training/sim2sim.py
Sim2SimConfigResolver:显式定义三类字段列表
- ALLOWLIST:允许 target 自由覆盖(
sim_backend, scene, play_steps, domain_rand, noise_config 等运行时/后端字段)
- WARNING_LIST:允许覆盖但打印 warning(
simulate_action_latency, ctrl_dt 等物理参数差异)
- DENYLIST:严格禁止覆盖,差异即报错(
obs_groups, action_scale, reward 权重、commands.ranges/sampling、policy.hidden_dims、empirical_normalization/obs_normalization 等影响策略兼容性的字段)
resolve_sim2sim_config(source_run_dir, target_cfg):读取 source run 的 run_config.json,按上述规则与 target cfg 合并,返回 resolved DictConfig。
ExperimentTracker 自动保存 contract_snapshot
在 run_config.json 中新增一个自动生成的 contract_snapshot 字段:
def extract_contract_snapshot(full_cfg: DictConfig) -> dict:
paths = DENYLIST | WARNING_LIST
return {path: OmegaConf.select(full_cfg, path) for path in paths}
新增 DENYLIST 字段时 snapshot 自动包含,无需手动维护。
Play 入口改造
在 play_rsl_rl()、play_appo()、play_offpolicy()、play_mlx_ppo() 的 create_env() 之前插入:
cfg = resolve_sim2sim_config(load_path_dir, cfg) or cfg
后续逻辑完全不变,实现最小侵入。
运行时维度校验(最后一道防线)
在 create_env() 之后、模型加载之前,增加轻量校验:
assert env.obs_groups_spec == source_snapshot["obs_groups_spec"]
assert env.action_space.shape[0] == source_snapshot["action_dim"]
关键设计决策
- 不修改任何 checkpoint (.pt) 格式:零算法侵入,天然兼容历史 checkpoint。
- 默认允许覆盖,DENYLIST 显式保护:不在任何列表中的字段(如
wandb_project, save_interval)target 可自由覆盖,避免误报。
default_angles 从目标 backend keyframe 重新推导:不强制恢复 source 值,避免 asset 元数据泄漏(符合 Cold-path asset access 原则)。
commands 拆分保护:commands.ranges/sampling(影响 obs 语义)在 DENYLIST;commands.limits(仅影响训练分布)在 ALLOWLIST。
Phase 2:YAML 分层(长期工程整洁度)
为高频 task(如 g1_walk_flat, go2_joystick_flat)引入 base.yaml:
# conf/ppo/task/g1_walk_flat/mujoco.yaml
defaults:
- base@_global_
- _self_
sim_backend: mujoco
env:
scene:
model_file: ...
base.yaml 承载 TaskCore(跨后端必须一致),backend YAML 仅含 backend-specific override。配合 CI 检查确保 backend YAML 不能覆盖 DENYLIST 字段。
Phase 3:CI 回归测试
添加 cross-backend sim2sim 测试用例(如 MuJoCo 训练 10 iters → Motrix play 验证不崩溃)。
Deliverable
src/unilab/training/sim2sim.py(Sim2SimConfigResolver + resolve_sim2sim_config)
ExperimentTracker 中自动 contract_snapshot 提取逻辑
- 四个 play 入口的
resolve_sim2sim_config 集成(rsl-rl / APPO / offpolicy / MLX PPO)
- 运行时维度校验(obs_groups_spec, action_dim)
- 单元测试
tests/test_sim2sim_resolver.py
AGENTS.md 更新(sim2sim 规范与字段归属说明)
Definition of Done
Validation Plan
- 在 MuJoCo 后端训练 g1_walk_flat(少量 iteration),保存 checkpoint。
- 在 Motrix 后端 play 该 checkpoint,验证策略能正常推理且不报错。
- 手动修改 Motrix YAML 的
env.action_scale,再次 play,验证 resolver 在 env 创建前报错并提示差异字段。
- 运行
make test-all 确认无回归。
Dependencies and Blockers
无外部依赖,纯仓库内部 infra 改动。
Problem
在 UniLab 中,用户可以在 MuJoCo 或 Motrix 后端训练策略,然后在另一个后端进行 sim2sim(play)验证。然而,当前 play 脚本完全依赖当前 Hydra 合成的配置(即目标后端的 YAML)来创建环境,而同一个 task 的
mujoco.yaml与motrix.yaml是独立维护的,经常出现不一致:algo.obs_groups结构不同(如 mujoco 用actor,motrix 用policy/critic)(?存疑,验证是否真的存在这个问题)env.action_scale不同(如 0.25 vs 0.5)env.reward权重、base_height_target、max_tilt_deg 不同env.commands、domain_rand、gait_phase_init_mode等关键参数不同当这些差异存在时,目标后端创建出的环境 obs/action 空间与训练时不匹配,导致策略网络输入维度错误或语义偏移,最终表现为崩溃或行为异常。由于 checkpoint(
.pt)仅保存权重,不携带训练时的配置,用户只能在运行时才能发现不一致,调试成本极高。Root Cause
.pt仅保存actor_state_dict等权重,没有保存训练时的 env/algo 配置。play_rsl_rl()等入口使用当前 Hydracfg调用BackendAdapter.build_play_env_cfg_override(),无法感知训练时的配置。base.yaml)来锁定跨后端必须一致的字段。Proposed Solution(Sidecar + Diff-Merge)
Phase 1:最小侵入的 Sidecar 方案
利用训练时已存在的
run_config.jsonsidecar(由ExperimentTracker.start()写入,包含完整 resolvedfull_cfg),实现"训练配置恢复 + 目标后端覆盖"的合并机制。新增模块
src/unilab/training/sim2sim.pySim2SimConfigResolver:显式定义三类字段列表sim_backend,scene,play_steps,domain_rand,noise_config等运行时/后端字段)simulate_action_latency,ctrl_dt等物理参数差异)obs_groups,action_scale,reward权重、commands.ranges/sampling、policy.hidden_dims、empirical_normalization/obs_normalization等影响策略兼容性的字段)resolve_sim2sim_config(source_run_dir, target_cfg):读取 source run 的run_config.json,按上述规则与 target cfg 合并,返回 resolvedDictConfig。ExperimentTracker自动保存contract_snapshot在
run_config.json中新增一个自动生成的contract_snapshot字段:新增 DENYLIST 字段时 snapshot 自动包含,无需手动维护。
Play 入口改造
在
play_rsl_rl()、play_appo()、play_offpolicy()、play_mlx_ppo()的create_env()之前插入:后续逻辑完全不变,实现最小侵入。
运行时维度校验(最后一道防线)
在
create_env()之后、模型加载之前,增加轻量校验:关键设计决策
wandb_project,save_interval)target 可自由覆盖,避免误报。default_angles从目标 backend keyframe 重新推导:不强制恢复 source 值,避免 asset 元数据泄漏(符合 Cold-path asset access 原则)。commands拆分保护:commands.ranges/sampling(影响 obs 语义)在 DENYLIST;commands.limits(仅影响训练分布)在 ALLOWLIST。Phase 2:YAML 分层(长期工程整洁度)
为高频 task(如
g1_walk_flat,go2_joystick_flat)引入base.yaml:base.yaml承载 TaskCore(跨后端必须一致),backend YAML 仅含 backend-specific override。配合 CI 检查确保 backend YAML 不能覆盖 DENYLIST 字段。Phase 3:CI 回归测试
添加 cross-backend sim2sim 测试用例(如 MuJoCo 训练 10 iters → Motrix play 验证不崩溃)。
Deliverable
src/unilab/training/sim2sim.py(Sim2SimConfigResolver+resolve_sim2sim_config)ExperimentTracker中自动contract_snapshot提取逻辑resolve_sim2sim_config集成(rsl-rl / APPO / offpolicy / MLX PPO)tests/test_sim2sim_resolver.pyAGENTS.md更新(sim2sim 规范与字段归属说明)Definition of Done
env.action_scale)时,Sim2SimConfigResolver在 env 创建前抛出CrossBackendIncompatibleError。contract_snapshot,即当前及历史 checkpoint)能 fallback 到当前 YAML 并打印 warning,不中断现有工作流。make test-all通过。AGENTS.md更新 sim2sim 相关规范。Validation Plan
env.action_scale,再次 play,验证 resolver 在 env 创建前报错并提示差异字段。make test-all确认无回归。Dependencies and Blockers
无外部依赖,纯仓库内部 infra 改动。