From 83a71fc6c081c2ddf8d94c4197d690b92d04bf8f Mon Sep 17 00:00:00 2001 From: Pepijn Date: Wed, 29 Jul 2026 19:20:01 +0200 Subject: [PATCH] refactor(g05): defer PandaOmron embodiment mapping --- docs/source/g05.mdx | 5 +- src/lerobot/policies/g05/configuration_g05.py | 25 +------ tests/policies/g05/test_g05.py | 74 ------------------- 3 files changed, 2 insertions(+), 102 deletions(-) diff --git a/docs/source/g05.mdx b/docs/source/g05.mdx index 0f208c94c..1a91d3822 100644 --- a/docs/source/g05.mdx +++ b/docs/source/g05.mdx @@ -60,10 +60,7 @@ The converted checkpoints are private under the LeRobot organization: `lerobot/g05_base` supplies those 27D model weights, both action heads, the ActionCodec tokenizer, and the released six-step R1 Lite processor/statistics -contract. OpenGalaxea's base release does not contain an `atomic_4` processor or -Atomic-4 dataset statistics. Loading the base weights for Atomic-4 therefore -requires an `atomic_4` `G05Config` plus statistics computed from the target -Atomic-4 dataset; reusing the R1 Lite statistics would be incorrect. +contract. ## Installation Requirements diff --git a/src/lerobot/policies/g05/configuration_g05.py b/src/lerobot/policies/g05/configuration_g05.py index e029fbf05..c693c4800 100644 --- a/src/lerobot/policies/g05/configuration_g05.py +++ b/src/lerobot/policies/g05/configuration_g05.py @@ -51,11 +51,6 @@ G05_CAMERA_PROFILES: dict[str, tuple[str, ...]] = { "observation.images.left_wrist_rgb", "observation.images.right_wrist_rgb", ), - "atomic_4": ( - "observation.images.robot0_agentview_left", - "observation.images.robot0_eye_in_hand", - "observation.images.robot0_agentview_right", - ), } G05_CAMERA_SIZE_PROFILES: dict[str, dict[str, tuple[int, int]]] = { @@ -64,7 +59,6 @@ G05_CAMERA_SIZE_PROFILES: dict[str, dict[str, tuple[int, int]]] = { "so100": dict.fromkeys(G05_CAMERA_PROFILES["so100"], (256, 256)), "galaxea_r1lite": dict.fromkeys(G05_CAMERA_PROFILES["galaxea_r1lite"], (256, 256)), "galaxea_r1pro": dict.fromkeys(G05_CAMERA_PROFILES["galaxea_r1pro"], (256, 256)), - "atomic_4": dict.fromkeys(G05_CAMERA_PROFILES["atomic_4"], (256, 256)), } @@ -126,8 +120,7 @@ def make_g05_cot_prompt_template( # Raw dimensions are inserted in these exact policy slots. The G0.5 shared layout is: # left_control[9] | left_gripper[1] | right_control[9] | right_gripper[1] | lower_body[7]. -# LIBERO uses only the right EEF delta and right gripper. atomic_4 is a single-arm mobile -# manipulator and therefore has a deliberately separate state/action map. +# LIBERO uses only the right EEF delta and right gripper. G05_EMBODIMENT_MAPPINGS: dict[str, dict[str, tuple[int, ...]]] = { "libero": { "state": (10, 11, 12, 13, 14, 15, 19), @@ -149,14 +142,6 @@ G05_EMBODIMENT_MAPPINGS: dict[str, dict[str, tuple[int, ...]]] = { "state": (0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 19), "action": (0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16, 19), }, - "atomic_4": { - # EEF relative xyz+quat -> right_control[0:7], base xyz+quat -> lower_body[0:7], - # the two parallel-jaw qpos values -> the two one-dimensional gripper slots. - "state": (10, 11, 12, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25, 26, 9, 19), - # EEF delta xyz+rpy -> right_control[0:6], gripper -> right_gripper, - # base motion[4] -> lower_body[0:4], control mode -> lower_body[4]. - "action": (10, 11, 12, 13, 14, 15, 19, 20, 21, 22, 23, 24), - }, } G05_POLICY_PARTS: dict[int, dict[str, int]] = { @@ -307,14 +292,6 @@ class G05Config(PreTrainedConfig): raise ValueError("At least one G0.5 action path must be enabled.") if self.embodiment not in G05_EMBODIMENT_MAPPINGS: raise ValueError(f"No named G0.5 embodiment mapping for {self.embodiment!r}.") - if self.embodiment == "atomic_4": - if self.policy_action_dim < 27 or self.policy_state_dim < 27: - raise ValueError( - "atomic_4 includes mobile-base/control-mode semantics and requires the 27D " - "G0.5 shared layout; a 20D LIBERO checkpoint is incompatible." - ) - if self.raw_action_dim != 12 or self.raw_state_dim != 16: - raise ValueError("atomic_4 requires raw_state_dim=16 and raw_action_dim=12.") mapping = G05_EMBODIMENT_MAPPINGS.get(self.embodiment) if mapping is not None: if len(mapping["state"]) != self.raw_state_dim: diff --git a/tests/policies/g05/test_g05.py b/tests/policies/g05/test_g05.py index 0c9173f63..5e0b72be9 100644 --- a/tests/policies/g05/test_g05.py +++ b/tests/policies/g05/test_g05.py @@ -237,36 +237,6 @@ def test_select_action_discards_tail_beyond_execution_window(): assert calls == 2 -def test_libero_and_atomic4_are_distinct_validated_mappings(): - with pytest.raises(ValueError, match="27D"): - G05Config( - checkpoint_profile="custom", - embodiment="atomic_4", - raw_state_dim=16, - raw_action_dim=12, - camera_order=( - "observation.images.robot0_agentview_left", - "observation.images.robot0_eye_in_hand", - "observation.images.robot0_agentview_right", - ), - ) - - cfg = G05Config( - checkpoint_profile="custom", - embodiment="atomic_4", - raw_state_dim=16, - raw_action_dim=12, - policy_state_dim=27, - policy_action_dim=27, - camera_order=( - "observation.images.robot0_agentview_left", - "observation.images.robot0_eye_in_hand", - "observation.images.robot0_agentview_right", - ), - ) - assert cfg.embodiment == "atomic_4" - - def test_libero_projection_mask_and_inverse_roundtrip(): config = _config() preprocessor, postprocessor = make_pre_post_processors(config) @@ -331,50 +301,6 @@ def test_lerobot_libero_two_finger_state_matches_author_first_qpos_contract(): assert torch.equal(processed[OBS_STATE][0, list(checkpoint_slots)], env_state[:7]) -def test_atomic4_projection_has_mobile_base_control_mode_and_exact_inverse(): - config = G05Config( - checkpoint_profile="custom", - embodiment="atomic_4", - raw_state_dim=16, - raw_action_dim=12, - policy_state_dim=27, - policy_action_dim=27, - normalization_mode="identity", - camera_order=( - "observation.images.robot0_agentview_left", - "observation.images.robot0_eye_in_hand", - "observation.images.robot0_agentview_right", - ), - input_features={ - OBS_STATE: PolicyFeature(type=FeatureType.STATE, shape=(16,)), - "observation.images.robot0_agentview_left": PolicyFeature( - type=FeatureType.VISUAL, shape=(3, 8, 8) - ), - "observation.images.robot0_eye_in_hand": PolicyFeature(type=FeatureType.VISUAL, shape=(3, 8, 8)), - "observation.images.robot0_agentview_right": PolicyFeature( - type=FeatureType.VISUAL, shape=(3, 8, 8) - ), - }, - output_features={ACTION: PolicyFeature(type=FeatureType.ACTION, shape=(12,))}, - device="cpu", - ) - preprocessor, postprocessor = make_pre_post_processors(config) - raw_action = torch.arange(12, dtype=torch.float32).repeat(3, 1) - batch = { - OBS_STATE: torch.arange(16, dtype=torch.float32), - ACTION: raw_action, - **{camera: torch.zeros(3, 8, 8) for camera in config.camera_order}, - "task": "atomic", - } - - processed = preprocessor(batch) - indices = G05_EMBODIMENT_MAPPINGS["atomic_4"]["action"] - assert torch.equal(processed[ACTION][..., list(indices)], raw_action) - assert torch.equal(postprocessor(processed[ACTION]), raw_action) - # Last five raw dimensions are base motion[4] and control mode. - assert indices[-5:] == (20, 21, 22, 23, 24) - - def test_quantile_mode_refuses_minmax_substitution(): config = _config(normalization_mode="q01_q99") stats = {