Cross-Backend Config Guard¶
When replaying across backends (train on one backend, eval on another), UniLab automatically checks whether the target backend config is compatible with the policy contract captured at training time, so a mismatched config can’t silently load a broken policy. This happens with no manual steps.
An example that replays successfully¶
For go2_joystick_flat, the MuJoCo and Motrix owners agree on every guarded field, so a cross-backend replay passes directly:
# 1) Train in MuJoCo, producing a checkpoint
uv run train --algo ppo --task go2_joystick_flat --sim mujoco
# 2) Replay the same checkpoint across backends in Motrix — the guard passes, playback runs
uv run eval --algo ppo --task go2_joystick_flat --sim motrix --load-run -1
How the guard chain works¶
At train time:
ExperimentTrackersnapshots the contract fields that define policy I/O intocontract_snapshotinrun_config.json(the checkpoint format is untouched, so historical checkpoints stay compatible).At replay time:
evalloads the target backend owner config selected by--sim(e.g.src/unilab/conf/ppo/task/go2_joystick_flat/motrix.yaml) and injectstraining.play_only=true. If the task has no owner config for the requested backend,evalfalls back to a sibling backend owner of the same task and re-applies the requested backend through the allowlistedtraining.sim_backendoverride (trainstill requires the owner config to exist); the guard chain below is unaffected.Before env creation: the play entrypoints (rsl_rl / appo / sac / td3 / flashsac / him_ppo) call
resolve_sim2sim_config, comparing the target config against the source run’s contract snapshot field by field.At weight load:
policy_load_dim_guardwraps checkpoint loading, re-raising cryptic tensor shape-mismatch errors as a clear sim2sim diagnostic.
What the guard covers¶
Fields are classified by dotted path into three tiers (see src/unilab/training/sim2sim.py):
Tier |
Behavior |
Fields |
|---|---|---|
DENYLIST |
Mismatch → |
|
WARNING_LIST |
Prints a warning, continues |
|
ALLOWLIST |
Free to override, not checked |
|
When DENYLIST fields differ¶
If the target backend’s DENYLIST fields differ from training (e.g. a task whose two owners use different action_scale values), the guard aborts before env creation and lists the diverging fields. Two ways to resolve:
Align the contract (recommended): make the target owner’s DENYLIST fields match the training backend, then replay.
Force through (at your own risk):
uv run eval ... training.sim2sim_strict=falsedowngrades DENYLIST mismatches to warnings.
Legacy runs: if
run_config.jsonhas nocontract_snapshot(older training), the guard skips with a warning instead of breaking your workflow.
Manager-Based snapshots store the complete typed observation and action declarations from
Hydra. A snapshot from before those fields existed cannot prove that its policy I/O is
equivalent to a Manager-Based target, so asymmetric presence fails closed. Set
training.sim2sim_strict=false only as an explicit user override; the load-time dimension
guard still remains active.