diff --git a/src/lerobot/policies/groot/modeling_groot.py b/src/lerobot/policies/groot/modeling_groot.py index cbfe647f7..f0e1c08d3 100644 --- a/src/lerobot/policies/groot/modeling_groot.py +++ b/src/lerobot/policies/groot/modeling_groot.py @@ -468,6 +468,14 @@ class GrootPolicy(PreTrainedPolicy): @torch.no_grad() def select_action(self, batch: dict[str, Tensor]) -> Tensor: """Select single action from action queue.""" + if getattr(self.config, "use_relative_actions", False): + raise NotImplementedError( + "GrootPolicy.select_action does not support relative-action policies because cached " + "relative chunk actions can be decoded against newer observation states. Use " + "predict_action_chunk and postprocess the full chunk before queuing actions, or use " + "the RTC/chunked rollout inference path." + ) + self.eval() if len(self._action_queue) == 0: diff --git a/src/lerobot/policies/groot/processor_groot.py b/src/lerobot/policies/groot/processor_groot.py index f37e2dbfd..6c68aa693 100644 --- a/src/lerobot/policies/groot/processor_groot.py +++ b/src/lerobot/policies/groot/processor_groot.py @@ -2158,6 +2158,12 @@ class GrootN17ActionDecodeStep(ProcessorStep): return transition action_np = action.detach().cpu().float().numpy() + if self.use_relative_action and action_np.ndim != 3: + raise NotImplementedError( + "GrootN17ActionDecodeStep cannot decode native relative actions one step at a time. " + "Decode the full action chunk returned by predict_action_chunk while the matching " + "GrootN17PackInputsStep state is still cached, then queue the decoded absolute actions." + ) # The sync action queue postprocesses popped actions as (B, D); decode # them as single-step (B, 1, D) chunks and squeeze the horizon back at # the end so both ranks share the chunk decode logic below. diff --git a/tests/policies/groot/test_groot_n1_7.py b/tests/policies/groot/test_groot_n1_7.py index 2fbf3c11a..4a45393f8 100644 --- a/tests/policies/groot/test_groot_n1_7.py +++ b/tests/policies/groot/test_groot_n1_7.py @@ -1324,6 +1324,55 @@ def test_groot_n1_7_action_decode_truncates_to_valid_horizon_for_relative_stats( torch.testing.assert_close(decoded[..., 5], torch.full((1, 16), 5.0)) +def test_groot_n1_7_action_decode_rejects_stepwise_native_relative_actions(): + raw_stats = { + "state": { + "single_arm": _stats([0.0] * 5), + "gripper": _stats([0.0]), + }, + "action": { + "single_arm": _stats([0.0] * 5), + "gripper": _stats([0.0]), + }, + "relative_action": { + "single_arm": _stats([0.0] * 5), + }, + } + modality_config = { + "state": { + "modality_keys": ["single_arm", "gripper"], + }, + "action": { + "modality_keys": ["single_arm", "gripper"], + "action_configs": [ + {"rep": "RELATIVE", "type": "NON_EEF", "format": "DEFAULT", "state_key": None}, + {"rep": "ABSOLUTE", "type": "NON_EEF", "format": "DEFAULT", "state_key": None}, + ], + }, + } + pack_step = GrootN17PackInputsStep( + raw_stats=raw_stats, + modality_config=modality_config, + normalize_min_max=False, + ) + pack_step( + { + TransitionKey.OBSERVATION: {OBS_STATE: torch.zeros(1, 6)}, + TransitionKey.COMPLEMENTARY_DATA: {}, + } + ) + decode_step = GrootN17ActionDecodeStep( + env_action_dim=6, + raw_stats=raw_stats, + modality_config=modality_config, + use_relative_action=True, + pack_step=pack_step, + ) + + with pytest.raises(NotImplementedError, match="cannot decode native relative actions one step at a time"): + decode_step({TransitionKey.ACTION: torch.zeros(1, 6)}) + + def test_groot_n1_7_action_decode_requires_gripper_key_for_libero_transform(): step = GrootN17ActionDecodeStep( env_action_dim=1, @@ -2324,6 +2373,14 @@ def test_groot_n1_7_libero_execution_horizon_uses_core_eight_action_cadence(tmp_ assert infer_groot_n1_7_action_execution_horizon(model_path, "libero_sim") == 8 +def test_groot_select_action_rejects_relative_action_policies(): + policy = object.__new__(GrootPolicy) + object.__setattr__(policy, "config", SimpleNamespace(use_relative_actions=True)) + + with pytest.raises(NotImplementedError, match="select_action does not support relative-action policies"): + policy.select_action({}) + + def test_groot_n1_7_select_action_uses_checkpoint_valid_horizon(tmp_path, monkeypatch): from lerobot.policies.groot.groot_n1_7 import GR00TN17