refactor(g05): defer PandaOmron embodiment mapping

This commit is contained in:
Pepijn
2026-07-29 19:20:01 +02:00
parent 47f845ac5d
commit 83a71fc6c0
3 changed files with 2 additions and 102 deletions
+1 -4
View File
@@ -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 `lerobot/g05_base` supplies those 27D model weights, both action heads, the
ActionCodec tokenizer, and the released six-step R1 Lite processor/statistics ActionCodec tokenizer, and the released six-step R1 Lite processor/statistics
contract. OpenGalaxea's base release does not contain an `atomic_4` processor or contract.
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.
## Installation Requirements ## Installation Requirements
+1 -24
View File
@@ -51,11 +51,6 @@ G05_CAMERA_PROFILES: dict[str, tuple[str, ...]] = {
"observation.images.left_wrist_rgb", "observation.images.left_wrist_rgb",
"observation.images.right_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]]] = { 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)), "so100": dict.fromkeys(G05_CAMERA_PROFILES["so100"], (256, 256)),
"galaxea_r1lite": dict.fromkeys(G05_CAMERA_PROFILES["galaxea_r1lite"], (256, 256)), "galaxea_r1lite": dict.fromkeys(G05_CAMERA_PROFILES["galaxea_r1lite"], (256, 256)),
"galaxea_r1pro": dict.fromkeys(G05_CAMERA_PROFILES["galaxea_r1pro"], (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: # 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]. # 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 # LIBERO uses only the right EEF delta and right gripper.
# manipulator and therefore has a deliberately separate state/action map.
G05_EMBODIMENT_MAPPINGS: dict[str, dict[str, tuple[int, ...]]] = { G05_EMBODIMENT_MAPPINGS: dict[str, dict[str, tuple[int, ...]]] = {
"libero": { "libero": {
"state": (10, 11, 12, 13, 14, 15, 19), "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), "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), "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]] = { 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.") raise ValueError("At least one G0.5 action path must be enabled.")
if self.embodiment not in G05_EMBODIMENT_MAPPINGS: if self.embodiment not in G05_EMBODIMENT_MAPPINGS:
raise ValueError(f"No named G0.5 embodiment mapping for {self.embodiment!r}.") 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) mapping = G05_EMBODIMENT_MAPPINGS.get(self.embodiment)
if mapping is not None: if mapping is not None:
if len(mapping["state"]) != self.raw_state_dim: if len(mapping["state"]) != self.raw_state_dim:
-74
View File
@@ -237,36 +237,6 @@ def test_select_action_discards_tail_beyond_execution_window():
assert calls == 2 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(): def test_libero_projection_mask_and_inverse_roundtrip():
config = _config() config = _config()
preprocessor, postprocessor = make_pre_post_processors(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]) 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(): def test_quantile_mode_refuses_minmax_substitution():
config = _config(normalization_mode="q01_q99") config = _config(normalization_mode="q01_q99")
stats = { stats = {