mirror of
https://github.com/huggingface/lerobot.git
synced 2026-08-08 17:39:44 +00:00
refactor(g05): defer PandaOmron embodiment mapping
This commit is contained in:
+1
-4
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user