|
| 1 | +# LIBERO + LIBERO-plus → RoboVerse 1:1 integration |
| 2 | + |
| 3 | +[LIBERO](https://github.com/Lifelong-Robot-Learning/LIBERO) (*Lifelong Robot |
| 4 | +Learning*, Liu et al. 2023) is a 130-task manipulation benchmark of robosuite / |
| 5 | +MuJoCo environments across five suites (`libero_spatial`, `libero_object`, |
| 6 | +`libero_goal`, `libero_10`, `libero_90`). |
| 7 | +[LIBERO-plus](https://github.com/sylvestf/LIBERO-plus) (*In-depth Robustness |
| 8 | +Analysis of VLA Models*, 2025) is a drop-in superset that expands every suite |
| 9 | +with thousands of **perturbation** variants — **10,120 tasks** across seven |
| 10 | +dimensions (object layout, camera viewpoint, robot init state, language rewrite, |
| 11 | +lighting, background texture, sensor noise). |
| 12 | + |
| 13 | +RoboVerse integrates both as **passthrough** task packs: registration is lazy |
| 14 | +and import-safe, and the env is the *native* LIBERO(-plus) `OffScreenRenderEnv`, |
| 15 | +so observations / dynamics / 128×128 camera bytes are **bitwise-identical** to |
| 16 | +upstream. On top of that we verify a **pure-MetaSim native reproduction** (load |
| 17 | +the scene into MetaSim's own MuJoCo handler) and a **bitwise OSC_POSE controller |
| 18 | +port** so LIBERO EE-delta policies can run *inside* MetaSim. |
| 19 | + |
| 20 | +## Status |
| 21 | + |
| 22 | +| Capability | Result | Where | |
| 23 | +|---|---|---| |
| 24 | +| LIBERO passthrough | **130 / 130** tasks, obs+step bitwise (Δ=0) | `roboverse_pack/tasks/libero/` | |
| 25 | +| LIBERO-plus passthrough | **10,120 / 10,120** tasks, state/reward/done bitwise (Δ=0) | `roboverse_pack/tasks/libero_plus/` | |
| 26 | +| demo-replay parity | passthrough == native, Δ=0 (380 demos) | `scripts/parity_liberoplus_passthrough.py` | |
| 27 | +| asset audit | 7/7 perturbation dims genuinely applied, **0 silent fallback** | `scripts/audit_liberoplus_assets.py` | |
| 28 | +| MetaSim MuJoCo migration | 6/6 dims: state-set Δ=0, **engine Δ=0** | `scripts/migrate_liberoplus_metasim.py` | |
| 29 | +| OSC_POSE port | **bitwise** — per-state joint-torque Δ = 5.55e-15 N·m | `scripts/osc/` | |
| 30 | +| BC policy (closed-loop) | clean 100 % / light 0 % / camera 50 % / noise 75 %; passthrough==native Δ=0 | `scripts/policy/` | |
| 31 | + |
| 32 | +MetaSim core changes: **0** (the passthrough reuses the unmodified upstream env; |
| 33 | +the MetaSim handler loads the scene MJCF verbatim). |
| 34 | + |
| 35 | +## Environment setup |
| 36 | + |
| 37 | +LIBERO pins `numpy<1.24` / `robosuite==1.4.0` / `bddl==1.0.1` / `mujoco==3.2.3`, |
| 38 | +which conflict with the default RoboVerse env — so install each in a **dedicated** |
| 39 | +conda env. The passthrough is a safe no-op in any env where LIBERO is not |
| 40 | +importable (registration registers nothing; the factory raises a clear error). |
| 41 | + |
| 42 | +```bash |
| 43 | +# base LIBERO (130 tasks) |
| 44 | +conda create -n libero1to1 python=3.8 -y && conda activate libero1to1 |
| 45 | +git clone https://github.com/Lifelong-Robot-Learning/LIBERO.git && pip install -e LIBERO |
| 46 | + |
| 47 | +# LIBERO-plus (10,120 tasks) — a separate env; it is a drop-in replacement of LIBERO |
| 48 | +conda create -n liberoplus python=3.8 -y && conda activate liberoplus |
| 49 | +git clone https://github.com/sylvestf/LIBERO-plus.git && pip install -e LIBERO-plus |
| 50 | +# + extract the LIBERO-plus asset bundle (6 GB) into libero/libero/assets |
| 51 | +``` |
| 52 | + |
| 53 | +The LIBERO-plus passthrough **self-bootstraps** its config: it writes |
| 54 | +`~/.libero_plus/config.yaml` pointing at the installed LIBERO-plus package's |
| 55 | +bddl / asset / init roots and sets `LIBERO_CONFIG_PATH` (never clobbering a |
| 56 | +user-set value or the base `~/.libero`). No manual config editing needed. |
| 57 | + |
| 58 | +## Usage |
| 59 | + |
| 60 | +```python |
| 61 | +import roboverse_pack.tasks.libero # auto-registers Libero/<suite>__<task> |
| 62 | +import roboverse_pack.tasks.libero_plus # auto-registers LiberoPlus/<suite>__<task> |
| 63 | +from roboverse_pack.tasks.libero_plus import make_liberoplus_env |
| 64 | + |
| 65 | +# build any of the 10,120 perturbation tasks by (suite, task index) |
| 66 | +env = make_liberoplus_env("libero_object", 7, seed=0) |
| 67 | +obs, reward, done, info = env.step([0.0] * 7) # native legacy-gym 4-tuple (kept for fidelity) |
| 68 | +``` |
| 69 | + |
| 70 | +## Reproduce — run commands |
| 71 | + |
| 72 | +All commands assume `MUJOCO_GL=egl` (headless) and the dedicated env. The |
| 73 | +LIBERO-plus runs additionally take `LIBERO_CONFIG_PATH=$HOME/.libero_plus`. |
| 74 | + |
| 75 | +```bash |
| 76 | +# --- passthrough bitwise tests (per env) --- |
| 77 | +MUJOCO_GL=egl python -m pytest tests/test_libero_passthrough.py -v # in libero1to1 |
| 78 | +MUJOCO_GL=egl python -m pytest tests/test_liberoplus_passthrough.py -v # in liberoplus |
| 79 | + |
| 80 | +# --- LIBERO-plus passthrough == native, all 7 perturbation dimensions --- |
| 81 | +LIBERO_CONFIG_PATH=$HOME/.libero_plus MUJOCO_GL=egl \ |
| 82 | + python -m scripts.parity_liberoplus_passthrough --per-dim 1 --steps 8 |
| 83 | + |
| 84 | +# --- asset audit: prove every perturbation actually changes the render --- |
| 85 | +LIBERO_CONFIG_PATH=$HOME/.libero_plus MUJOCO_GL=egl \ |
| 86 | + python -m scripts.audit_liberoplus_assets --suites libero_spatial libero_object libero_goal libero_10 |
| 87 | + |
| 88 | +# --- migrate the scene into MetaSim's own MuJoCo backend (state + engine 1:1) --- |
| 89 | +LIBERO_CONFIG_PATH=$HOME/.libero_plus MUJOCO_GL=egl \ |
| 90 | + python -m scripts.migrate_liberoplus_metasim --suite libero_spatial |
| 91 | + |
| 92 | +# --- OSC_POSE controller bitwise parity vs robosuite (for in-MetaSim control) --- |
| 93 | +LIBERO_CONFIG_PATH=$HOME/.libero_plus MUJOCO_GL=egl \ |
| 94 | + python -m scripts.osc.parity_osc_vs_robosuite --steps 130 --precontact 40 |
| 95 | + |
| 96 | +# --- closed-loop BC policy: train (GPU env) then eval through the passthrough (CPU) --- |
| 97 | +python -m scripts.policy.train_bc_libero \ |
| 98 | + --demos third_party/libero_datasets/libero_object/<task>_demo.hdf5 --epochs 100 \ |
| 99 | + --out scripts/policy/ckpt/bc.pt |
| 100 | +LIBERO_CONFIG_PATH=$HOME/.libero_plus MUJOCO_GL=egl \ |
| 101 | + python -m scripts.policy.eval_bc_liberoplus --ckpt scripts/policy/ckpt/bc.pt \ |
| 102 | + --base <task> --suite libero_object --episodes 8 |
| 103 | +``` |
| 104 | + |
| 105 | +## Side-by-side: native LIBERO vs MetaSim |
| 106 | + |
| 107 | +Beyond "passthrough == native by construction", we load each task's own combined |
| 108 | +MJCF (Franka + objects + arena + camera) into **MetaSim's MuJoCo handler** and |
| 109 | +reproduce the demo — proving the MetaSim backend simulates the LIBERO scene 1:1. |
| 110 | + |
| 111 | +**Stage 1 — kinematics** (per-frame `set_states`, geometry/pose 1:1): |
| 112 | + |
| 113 | +<video controls width="640" src="../../_static/integrations/libero/sidebyside_kinematic_spatial_task0.mp4"></video> |
| 114 | + |
| 115 | +**Stage 2 — dynamics** (MetaSim's engine steps under the captured ctrl): |
| 116 | + |
| 117 | +<video controls width="640" src="../../_static/integrations/libero/sidebyside_dynamics_spatial_task0.mp4"></video> |
| 118 | + |
| 119 | +Left = native LIBERO `agentview_rgb`, right = MetaSim render. Per-frame |
| 120 | +`max|qpos − recorded| = 0.0` (exact); the MetaSim engine step matches reference |
| 121 | +MuJoCo to `max|Δ| = 1.6e-4` (float accumulation). Residual pixel MAE ≈ 2.5–5/255 |
| 122 | +is renderer config (lighting / anti-aliasing), not physics. |
| 123 | + |
| 124 | +### Demo-replay parity (all 5 suites) |
| 125 | + |
| 126 | + |
| 127 | + |
| 128 | + |
| 129 | +| suite | tasks | demos | pt-vs-native max\|Δ\| | success (pt / native) | |
| 130 | +|---|---|---|---|---| |
| 131 | +| libero_spatial | 10 | 50 | **0** | 41/50 · 41/50 | |
| 132 | +| libero_object | 10 | 50 | **0** | 43/50 · 43/50 | |
| 133 | +| libero_goal | 10 | 50 | **0** | 40/50 · 40/50 | |
| 134 | +| libero_10 | 10 | 50 | **0** | 37/50 · 37/50 | |
| 135 | +| libero_90 | 90 | 180 | **0** | 154/180 · 154/180 | |
| 136 | + |
| 137 | +The load-bearing number is **passthrough-vs-native = 0** (bitwise across all |
| 138 | +380 demos). The ~65 non-successful replays diverge *identically* on both backends |
| 139 | +(LIBERO's intrinsic open-loop OSC_POSE replay non-determinism, not a RoboVerse gap). |
| 140 | + |
| 141 | +### OSC_POSE controller parity |
| 142 | + |
| 143 | +`scripts/osc/parity_osc_vs_robosuite.py` reports: |
| 144 | + |
| 145 | +``` |
| 146 | +(A) per-step torque parity at every real state incl. contact (130 states): |
| 147 | + joint-torque max|Δ| = 5.55e-15 N·m <- bitwise control-law faithfulness |
| 148 | +(B) pre-contact open-loop rollout on MJB-exact model (40 steps): |
| 149 | + arm-qpos max|Δ| = 1.25e-05 rad |
| 150 | +``` |
| 151 | + |
| 152 | +The ported `MujocoOSCPose` reuses robosuite's `control_utils` / `transform_utils` |
| 153 | +math verbatim, reimplementing only the sim-data access (`mj_fullM`, `mj_jacSite`, |
| 154 | +EE pose/vel, `qfrc_bias`) on MetaSim's dm_control `Physics`. It emits joint |
| 155 | +torques into the existing `dof_torque` path → additive, opt-in, zero blast radius. |
| 156 | + |
| 157 | +### Real policy robustness (closed-loop, via the passthrough) |
| 158 | + |
| 159 | +An ACT-style BC policy trained on **clean** `libero_object` demos, evaluated |
| 160 | +closed-loop on LIBERO-plus perturbations — exactly the robustness signal the |
| 161 | +benchmark measures: |
| 162 | + |
| 163 | +| variant | clean | light | sensor-noise | camera | |
| 164 | +|---|---|---|---|---| |
| 165 | +| success | **8/8 = 1.00** | 0/8 | 6/8 | 4/8 | |
| 166 | + |
| 167 | +`passthrough == native` under the policy: full-rollout state max\|Δ\| = **0**. |
| 168 | + |
| 169 | +## Details and gotchas |
| 170 | + |
| 171 | +- **Config bootstrap.** LIBERO-plus reads bddl/asset/init roots from |
| 172 | + `$LIBERO_CONFIG_PATH/config.yaml` (default `~/.libero`). Because base LIBERO and |
| 173 | + LIBERO-plus share the package name, the passthrough self-writes a dedicated |
| 174 | + `~/.libero_plus` config from the *installed* LIBERO-plus package so the 10k |
| 175 | + perturbation tasks resolve, without touching the base config. |
| 176 | +- **Perturbation encoding.** File-based dims (background texture `_table_N`, |
| 177 | + lighting `_light_N`, layout `_add_N`) are real BDDL files; parametric dims |
| 178 | + (camera `_view_…`, robot init `_initstate_…`, language `_language_N`, noise |
| 179 | + `_noise_N`) are synthetic descriptors that the env wrapper parses to apply the |
| 180 | + perturbation — so the passthrough must **not** `os.path.isfile` them; it uses |
| 181 | + the benchmark's authoritative `get_task_bddl_file_path`. |
| 182 | +- **Global EGL render context.** robosuite/MuJoCo share a process-global EGL |
| 183 | + context — two `OffScreenRenderEnv` alive at once clobber each other's GL |
| 184 | + textures, so a texture-only perturbation reads as "0 effect". **Render one env |
| 185 | + at a time** (build → render → close). This is required for any side-by-side or |
| 186 | + batched rendering, and is why the asset audit / parity harnesses are sequential. |
| 187 | +- **Sensor-noise is upstream-stochastic.** Noise (motion/gaussian/fog/glass blur) |
| 188 | + is added to `agentview_image` *after* render with an unseeded `np.random` — the |
| 189 | + physics/state/reward/done are unaffected (bitwise); only the corrupted image |
| 190 | + differs across interleaved runs. It reproduces when each env runs in isolation. |
| 191 | +- **Lossless model transfer.** `env.sim.model.get_xml()` → reload is lossy on |
| 192 | + mesh inertias; the binary `mujoco.mj_saveModel` / `from_binary_path` (MJB) path |
| 193 | + is lossless (inertials Δ=0) — use MJB when an exact MetaSim model is required. |
| 194 | +- **OSC sticky orientation goal.** robosuite only updates `goal_ori` when the |
| 195 | + orientation delta is non-zero (a *sticky* goal); the port must match this or it |
| 196 | + drifts under near-zero wrist deltas. With the fix the control law is bitwise. |
| 197 | +- **GPU split (sm_120).** The LIBERO sim env pins py3.8 / torch 2.4.1, which can't |
| 198 | + use an sm_120 GPU (RTX 5090). Train policies in an sm_120-capable env (torch |
| 199 | + ≥2.7 / cu128) and run closed-loop eval with CPU inference in the sim env, or via |
| 200 | + a small sim↔policy socket bridge (`scripts/policy/bridge_*`). |
| 201 | + |
| 202 | +## Scope notes (honest) |
| 203 | + |
| 204 | +- The passthrough is **MuJoCo-only by design**: LIBERO is a robosuite/MuJoCo |
| 205 | + benchmark; porting its MJCF to SAPIEN/Newton needs asset re-authoring + a |
| 206 | + different contact model = an approximate cross-sim port, not 1:1. |
| 207 | +- The BC policy is a compact single-task demonstration (pipeline + robustness + |
| 208 | + passthrough==native), not a SOTA reproduction. The official |
| 209 | + `Sylvest/openvla-7b-oft-finetuned-libero-plus` checkpoint runs through the same |
| 210 | + bridge for absolute benchmark numbers. |
0 commit comments