diff --git a/docs/source/inference.mdx b/docs/source/inference.mdx index 55358568a..bdb47e978 100644 --- a/docs/source/inference.mdx +++ b/docs/source/inference.mdx @@ -239,8 +239,7 @@ MUJOCO_GL=egl lerobot-rollout \ Open `http://localhost:8010` for the live simulator view. Add `--sim.direct_subtask` to bypass the language planner and make each typed prompt -the action policy's current subtask. The legacy `lerobot-language-runtime` -command remains as a compatibility alias. +the action policy's current subtask. --- diff --git a/pyproject.toml b/pyproject.toml index 70abbf425..6e74745f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -358,8 +358,6 @@ lerobot-edit-dataset="lerobot.scripts.lerobot_edit_dataset:main" lerobot-setup-can="lerobot.scripts.lerobot_setup_can:main" lerobot-annotate="lerobot.scripts.lerobot_annotate:main" lerobot-rollout="lerobot.scripts.lerobot_rollout:main" -# Backward-compatible alias; interactive language deployment is part of lerobot-rollout. -lerobot-language-runtime="lerobot.scripts.lerobot_language_runtime:main" # ---------------- Tool Configurations ---------------- diff --git a/src/lerobot/policies/pi052/configuration_pi052.py b/src/lerobot/policies/pi052/configuration_pi052.py index 2afd76ef4..6442c33d1 100644 --- a/src/lerobot/policies/pi052/configuration_pi052.py +++ b/src/lerobot/policies/pi052/configuration_pi052.py @@ -146,15 +146,6 @@ class PI052Config(PI05Config): Degrades gracefully to BF16 if ``kernels`` / the FlashRT packages are missing. Default off keeps behaviour identical to the BF16 path.""" - use_hf_kernels: bool = True - """Deprecated. Liger HF kernels are patched unconditionally by - ``_enable_hf_kernels`` — this field is retained as a no-op for - backward compatibility with checkpoints saved before commit - d70c8104 (which still serialize ``use_hf_kernels: true`` into - ``config.json``). Loading those configs would otherwise raise - ``DecodingError: The fields use_hf_kernels are not valid for - PI052Config`` (job 22164492). Remove in a future major bump.""" - use_flex_attention: bool = False """Accepted for checkpoint-config compatibility only — no-op in this branch. Newer training runs serialize ``use_flex_attention`` into ``config.json`` to diff --git a/src/lerobot/policies/pi052/modeling_pi052.py b/src/lerobot/policies/pi052/modeling_pi052.py index 082bf5e2a..4114b88e4 100644 --- a/src/lerobot/policies/pi052/modeling_pi052.py +++ b/src/lerobot/policies/pi052/modeling_pi052.py @@ -979,15 +979,11 @@ class PI052Policy(PreTrainedPolicy): "PI052: knowledge insulation enabled — action→VLM K/V gradients are blocked in attention." ) - # Size per-environment inference state lazily; the scalar mirrors env 0 for compatibility. + # Size per-environment inference state lazily. self.last_subtasks: list[str] | None = None self.last_subtasks_raw: list[str] | None = None self.last_subtasks_source: list[str] | None = None self._last_good_subtasks: list[str | None] | None = None - self.last_subtask: str | None = None - self.last_subtask_raw: str | None = None - self.last_subtask_source: str = "unset" - self.last_subtask_debug: str = "" def reset(self): """Reset action and high-level inference state.""" @@ -1000,10 +996,6 @@ class PI052Policy(PreTrainedPolicy): self.last_subtasks_raw = None self.last_subtasks_source = None self._last_good_subtasks = None - self.last_subtask = None - self.last_subtask_raw = None - self.last_subtask_source = "unset" - self.last_subtask_debug = "" # Counts action chunks since the last subtask (re)generation, so the # subtask can be held across several chunks (see subtask_replan_steps). self._subtask_chunk_counter = 0 @@ -1828,11 +1820,6 @@ class PI052Policy(PreTrainedPolicy): tokens, masks = self._stack_token_rows(rows, tokenizer) - # Scalar aliases mirror env 0 for back-compat / single-env overlays. - self.last_subtask = self.last_subtasks[0] if self.last_subtasks else None - self.last_subtask_raw = self.last_subtasks_raw[0] if self.last_subtasks_raw else None - self.last_subtask_source = self.last_subtasks_source[0] if self.last_subtasks_source else "unset" - out = dict(batch) out[OBS_LANGUAGE_TOKENS] = tokens out[OBS_LANGUAGE_ATTENTION_MASK] = masks diff --git a/src/lerobot/runtime/cli.py b/src/lerobot/runtime/cli.py index 152752bee..eaab65eda 100644 --- a/src/lerobot/runtime/cli.py +++ b/src/lerobot/runtime/cli.py @@ -27,7 +27,6 @@ from typing import Any from .adapter import GenerationConfig from .language_runtime import LanguageConditionedPolicyAdapter, LanguageConditionedRuntime -from .repl import _emit logger = logging.getLogger("lerobot.runtime") @@ -586,7 +585,7 @@ def _handle_slash_command(runtime: Any, line: str) -> bool: runtime.set_task(rest) # New task → drop the stale subtask so the high-level # loop regenerates one for the new goal. - runtime.state["current_subtask"] = None + runtime.state.set_context("subtask", None) print(f"[runtime] action — task: {rest!r}", flush=True) elif runtime.state.get("task"): print( @@ -643,17 +642,16 @@ def _make_state_panel_renderer( console.print( " [dim]commands:[/] [bold]/action[/] run · [bold]/help[/] · [bold]stop[/]" ) - for key, label in ( - ("task", "task"), - ("current_subtask", "subtask"), - ("current_plan", "plan"), - ("current_memory", "memory"), - ): - value = st.get(key) + display_values = { + "task": st.get("task"), + **(st.get("language_context") or {}), + } + for key in ("task", "subtask", "plan", "memory"): + value = display_values.get(key) if value: - console.print(f" [bold cyan]{label:<8}[/] {value}") + console.print(f" [bold cyan]{key:<8}[/] {value}") else: - console.print(f" [dim]{label:<8} (not set)[/]") + console.print(f" [dim]{key:<8} (not set)[/]") queue_len = ( len(st["action_queue"]) if isinstance(st.get("action_queue"), (list, tuple)) or hasattr(st.get("action_queue"), "__len__") @@ -728,7 +726,7 @@ def run( *, adapter_factory: Callable[[Any, GenerationConfig], LanguageConditionedPolicyAdapter] | None = None, panel_label: str | None = None, - prog: str = "lerobot-language-runtime", + prog: str = "lerobot-rollout", ) -> int: """Run the interactive language-conditioned runtime CLI. @@ -821,7 +819,7 @@ def run( rt = runtime_box.get("rt") if rt is None: return args.task - return rt.state.get("current_subtask") or rt.state.get("task") or args.task + return rt.state.language_context.get("subtask") or rt.state.task or args.task if sim_mode: from lerobot.runtime.sim_robocasa import RoboCasaSimBackend # noqa: PLC0415 @@ -936,7 +934,7 @@ def _run_sim_interactive( runtime.set_task(initial_task) # In direct-subtask mode the typed text IS the subtask; otherwise clear # it so the model generates one. - runtime.state["current_subtask"] = initial_task if direct_subtask else None + runtime.state.set_context("subtask", initial_task if direct_subtask else None) runtime.state["mode"] = "action" # Keep the terminal quiet while the browser renders the rollout. @@ -985,7 +983,7 @@ def _run_sim_interactive( elif low in {"/reset", "reset"}: sim_backend.reset_scene() _clear_action_queue(runtime) - runtime.state["current_subtask"] = None + runtime.state.set_context("subtask", None) if hasattr(runtime.policy, "reset"): runtime.policy.reset() print("[reset] new kitchen scene", flush=True) @@ -994,7 +992,7 @@ def _run_sim_interactive( runtime.set_task(cmd) # Direct mode: the typed text is the subtask itself; # otherwise clear it so the model regenerates one. - runtime.state["current_subtask"] = cmd if direct_subtask else None + runtime.state.set_context("subtask", cmd if direct_subtask else None) _clear_action_queue(runtime) adapter = getattr(runtime, "policy_adapter", None) if adapter is not None and hasattr(adapter, "_chunks_until_regen"): @@ -1043,7 +1041,7 @@ def _run_robot_interactive( if initial_task: runtime.set_task(initial_task) - runtime.state["current_subtask"] = initial_task if direct_subtask else None + runtime.state.set_context("subtask", initial_task if direct_subtask else None) # An explicit initial task starts immediately; otherwise the robot stays paused. runtime.state["mode"] = "action" @@ -1092,7 +1090,7 @@ def _run_robot_interactive( else: # New command: switch task/subtask immediately and regenerate. runtime.set_task(line) - runtime.state["current_subtask"] = line if direct_subtask else None + runtime.state.set_context("subtask", line if direct_subtask else None) _clear_action_queue(runtime) adapter = getattr(runtime, "policy_adapter", None) if adapter is not None and hasattr(adapter, "_chunks_until_regen"): @@ -1172,7 +1170,7 @@ def _run_repl( _redraw(last_logs) continue runtime.state["recent_interjection"] = line - _emit(runtime.state, "user_interjection") + runtime.state.emit("user_interjection") last_logs = runtime.step_once() or [] _redraw(last_logs) diff --git a/src/lerobot/runtime/language_runtime.py b/src/lerobot/runtime/language_runtime.py index dbb1b7263..c56f78afe 100644 --- a/src/lerobot/runtime/language_runtime.py +++ b/src/lerobot/runtime/language_runtime.py @@ -42,14 +42,6 @@ class RuntimeState: action_deadline: float | None = None extra: dict[str, Any] = field(default_factory=dict) - _ALIASES = { - "current_plan": ("language_context", "plan"), - "current_subtask": ("language_context", "subtask"), - "current_memory": ("language_context", "memory"), - "events_this_tick": ("events", None), - "_tick": ("tick", None), - } - def emit(self, event_name: str) -> None: self.events.add(event_name) @@ -88,11 +80,6 @@ class RuntimeState: return default def __getitem__(self, key: str) -> Any: - alias = self._ALIASES.get(key) - if alias is not None: - target, subkey = alias - value = getattr(self, target) - return value if subkey is None else value.get(subkey) if hasattr(self, key): return getattr(self, key) if key in self.extra: @@ -100,16 +87,6 @@ class RuntimeState: raise KeyError(key) def __setitem__(self, key: str, value: Any) -> None: - alias = self._ALIASES.get(key) - if alias is not None: - target, subkey = alias - if subkey is None: - setattr(self, target, value) - elif value is None: - getattr(self, target).pop(subkey, None) - else: - getattr(self, target)[subkey] = value - return if hasattr(self, key): setattr(self, key, value) else: diff --git a/src/lerobot/runtime/repl.py b/src/lerobot/runtime/repl.py deleted file mode 100644 index 8831350db..000000000 --- a/src/lerobot/runtime/repl.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2026 The HuggingFace Inc. team. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Small event helper shared by the language-runtime command loops.""" - -from __future__ import annotations - -from typing import Any - - -def _emit(state: Any, event_name: str) -> None: - if hasattr(state, "emit"): - state.emit(event_name) - else: - state.setdefault("events_this_tick", []).append(event_name) diff --git a/src/lerobot/runtime/sim_robocasa.py b/src/lerobot/runtime/sim_robocasa.py index d6cf82704..9b5fdfcae 100644 --- a/src/lerobot/runtime/sim_robocasa.py +++ b/src/lerobot/runtime/sim_robocasa.py @@ -211,7 +211,7 @@ class RoboCasaSimBackend: def bind_runtime(self, runtime: Any) -> None: """Wire live task/subtask/memory getters from the runtime state.""" self._task_getter = lambda: runtime.state.get("task") - self._subtask_getter = lambda: runtime.state.get("current_subtask") + self._subtask_getter = lambda: runtime.state.language_context.get("subtask") self._memory_getter = lambda: (runtime.state.get("language_context") or {}).get("memory") def _scene_description(self) -> str: diff --git a/src/lerobot/scripts/lerobot_eval.py b/src/lerobot/scripts/lerobot_eval.py index da680b586..d888c7d61 100644 --- a/src/lerobot/scripts/lerobot_eval.py +++ b/src/lerobot/scripts/lerobot_eval.py @@ -505,13 +505,10 @@ def eval_policy( tasks = list(env.call("task")) except (AttributeError, NotImplementedError): tasks = None - # Per-env subtasks when available (batched hierarchical policies); - # fall back to the scalar last_subtask for single-env / other policies. subtasks = getattr(policy, "last_subtasks", None) - subtask_scalar = getattr(policy, "last_subtask", None) annotated = [] for i in range(frames.shape[0]): - subtask_i = subtasks[i] if subtasks is not None and i < len(subtasks) else subtask_scalar + subtask_i = subtasks[i] if subtasks is not None and i < len(subtasks) else None annotated.append( _annotate_eval_frames( frames[i : i + 1], diff --git a/src/lerobot/scripts/lerobot_language_runtime.py b/src/lerobot/scripts/lerobot_language_runtime.py deleted file mode 100644 index 708fdeb0d..000000000 --- a/src/lerobot/scripts/lerobot_language_runtime.py +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env python -# Copyright 2026 The HuggingFace Inc. team. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Compatibility entry point for ``lerobot-language-runtime``. - -New commands should use ``lerobot-rollout --language`` or ``--sim``. -""" - -from __future__ import annotations - -import sys - - -def main(argv: list[str] | None = None) -> int: - from lerobot.runtime.cli import run - - return run(argv) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tests/runtime/test_language_runtime.py b/tests/runtime/test_language_runtime.py index 584fb1b01..af21b5e20 100644 --- a/tests/runtime/test_language_runtime.py +++ b/tests/runtime/test_language_runtime.py @@ -14,7 +14,6 @@ from lerobot.runtime import ( LanguageConditionedRuntime, - RuntimeState, ) @@ -70,12 +69,3 @@ def test_runtime_handles_user_interjection(): assert "please say ok" in adapter.interjections assert runtime.state.language_context["plan"] == "new plan" - - -def test_runtime_state_aliases_legacy_keys_to_language_context(): - state = RuntimeState() - state["current_subtask"] = "open drawer" - state["current_memory"] = "drawer open" - - assert state.get("current_subtask") == "open drawer" - assert state.language_context == {"subtask": "open drawer", "memory": "drawer open"}