mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 02:06:15 +00:00
refactor(runtime): remove gibberish filtering
This commit is contained in:
@@ -1751,8 +1751,6 @@ class PI052Policy(PreTrainedPolicy):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
def _generate_low_level_subtask(self, obs_i: dict[str, Tensor], task: str, i: int) -> str:
|
def _generate_low_level_subtask(self, obs_i: dict[str, Tensor], task: str, i: int) -> str:
|
||||||
from lerobot.runtime.adapter import looks_like_gibberish as _looks_like_gibberish # noqa: PLC0415
|
|
||||||
|
|
||||||
from .inference.pi052_adapter import _generate_with_policy # noqa: PLC0415
|
from .inference.pi052_adapter import _generate_with_policy # noqa: PLC0415
|
||||||
|
|
||||||
msg = ""
|
msg = ""
|
||||||
@@ -1767,7 +1765,7 @@ class PI052Policy(PreTrainedPolicy):
|
|||||||
self.last_subtasks_raw[i] = msg or ""
|
self.last_subtasks_raw[i] = msg or ""
|
||||||
|
|
||||||
# Feed the generated subtask verbatim, matching low-level training.
|
# Feed the generated subtask verbatim, matching low-level training.
|
||||||
if msg and not _looks_like_gibberish(msg):
|
if msg:
|
||||||
subtask = " ".join(msg.strip().split())
|
subtask = " ".join(msg.strip().split())
|
||||||
self._last_good_subtasks[i] = subtask
|
self._last_good_subtasks[i] = subtask
|
||||||
self.last_subtasks[i] = subtask
|
self.last_subtasks[i] = subtask
|
||||||
@@ -1779,8 +1777,6 @@ class PI052Policy(PreTrainedPolicy):
|
|||||||
debug = getattr(self, "_last_select_message_debug", "") or ""
|
debug = getattr(self, "_last_select_message_debug", "") or ""
|
||||||
if not task:
|
if not task:
|
||||||
reason = "No task string was available in the batch."
|
reason = "No task string was available in the batch."
|
||||||
elif msg:
|
|
||||||
reason = f"Rejected generated subtask: {msg!r}"
|
|
||||||
else:
|
else:
|
||||||
reason = f"Empty generated subtask. {debug}".strip()
|
reason = f"Empty generated subtask. {debug}".strip()
|
||||||
if self._last_good_subtasks[i]:
|
if self._last_good_subtasks[i]:
|
||||||
|
|||||||
@@ -43,11 +43,10 @@ class GenerationConfig:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LanguageDiagnostics:
|
class LanguageDiagnostics:
|
||||||
"""Runtime-panel rejection and repeat counters keyed by text kind."""
|
"""Runtime-panel generation counters keyed by text kind."""
|
||||||
|
|
||||||
last_raw: dict[str, str] = field(default_factory=dict)
|
last_raw: dict[str, str] = field(default_factory=dict)
|
||||||
empty: dict[str, int] = field(default_factory=dict)
|
empty: dict[str, int] = field(default_factory=dict)
|
||||||
gibberish: dict[str, int] = field(default_factory=dict)
|
|
||||||
repeat: int = 0
|
repeat: int = 0
|
||||||
|
|
||||||
def _bump(self, table: dict[str, int], kind: str) -> int:
|
def _bump(self, table: dict[str, int], kind: str) -> int:
|
||||||
@@ -96,9 +95,9 @@ class BaseLanguageAdapter(ABC):
|
|||||||
state.set_context("plan", plan, label="plan")
|
state.set_context("plan", plan, label="plan")
|
||||||
|
|
||||||
def plan_from_text(self, text: str) -> str:
|
def plan_from_text(self, text: str) -> str:
|
||||||
"""Strip ``<say>`` speech markers and reject gibberish plans."""
|
"""Strip ``<say>`` speech markers from a generated plan."""
|
||||||
plan, _speech = split_plan_and_say(text)
|
plan, _speech = split_plan_and_say(text)
|
||||||
return "" if looks_like_gibberish(plan) else plan
|
return plan
|
||||||
|
|
||||||
def _regenerate_context(self, observation: dict[str, Any] | None, state: RuntimeState) -> None:
|
def _regenerate_context(self, observation: dict[str, Any] | None, state: RuntimeState) -> None:
|
||||||
"""Default hierarchy: regenerate the subtask, then memory when it changes.
|
"""Default hierarchy: regenerate the subtask, then memory when it changes.
|
||||||
@@ -127,7 +126,7 @@ class BaseLanguageAdapter(ABC):
|
|||||||
def _generate_filtered(
|
def _generate_filtered(
|
||||||
self, kind: str, observation: dict[str, Any] | None, state: RuntimeState
|
self, kind: str, observation: dict[str, Any] | None, state: RuntimeState
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
"""Generate one ``kind``, record diagnostics, drop empty / gibberish output."""
|
"""Generate one ``kind``, record diagnostics, and drop empty output."""
|
||||||
text = self.generate_text(kind, observation, state)
|
text = self.generate_text(kind, observation, state)
|
||||||
self.diag.last_raw[kind] = text or ""
|
self.diag.last_raw[kind] = text or ""
|
||||||
if not text:
|
if not text:
|
||||||
@@ -135,11 +134,6 @@ class BaseLanguageAdapter(ABC):
|
|||||||
if count == 1 or count % 5 == 0:
|
if count == 1 or count % 5 == 0:
|
||||||
state.log(f" [info] {kind} gen returned empty (x{count})")
|
state.log(f" [info] {kind} gen returned empty (x{count})")
|
||||||
return None
|
return None
|
||||||
if looks_like_gibberish(text):
|
|
||||||
count = self.diag._bump(self.diag.gibberish, kind)
|
|
||||||
if count == 1 or count % 30 == 0:
|
|
||||||
state.log(f" [info] {kind} gen rejected (gibberish x{count}): {text[:60]!r}")
|
|
||||||
return None
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
@@ -159,29 +153,6 @@ class DirectTaskPolicyAdapter(BaseLanguageAdapter):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def looks_like_gibberish(text: str) -> bool:
|
|
||||||
"""Heuristic filter for malformed / collapsed LM-head output."""
|
|
||||||
if not text or not text.strip():
|
|
||||||
return True
|
|
||||||
stripped = text.strip()
|
|
||||||
alpha = sum(1 for c in stripped if c.isalpha())
|
|
||||||
if alpha < max(3, len(stripped) // 8):
|
|
||||||
return True
|
|
||||||
if stripped.startswith('":') and stripped.count('"') > stripped.count(" "):
|
|
||||||
return True
|
|
||||||
if len(set(stripped)) <= 2 and len(stripped) > 4:
|
|
||||||
return True
|
|
||||||
cleaned = stripped.replace("\n", " ").replace(":", " ")
|
|
||||||
for marker in ("Assistant", "User", "Ass "):
|
|
||||||
if marker in cleaned and len(cleaned.split()) < 4:
|
|
||||||
return True
|
|
||||||
tokens = [t for t in cleaned.split() if any(c.isalpha() for c in t)]
|
|
||||||
unique_alpha = {t.lower() for t in tokens}
|
|
||||||
if len(unique_alpha) < 3 and len(stripped) < 80:
|
|
||||||
return True
|
|
||||||
return len(tokens) >= 8 and len(unique_alpha) <= max(3, len(tokens) // 10)
|
|
||||||
|
|
||||||
|
|
||||||
def split_plan_and_say(text: str) -> tuple[str, str]:
|
def split_plan_and_say(text: str) -> tuple[str, str]:
|
||||||
"""Split ``plan <say>speech</say>`` into ``(plan, speech)``."""
|
"""Split ``plan <say>speech</say>`` into ``(plan, speech)``."""
|
||||||
if not text:
|
if not text:
|
||||||
|
|||||||
@@ -660,24 +660,19 @@ def _make_state_panel_renderer(
|
|||||||
dispatched = int(st.get("actions_dispatched") or 0)
|
dispatched = int(st.get("actions_dispatched") or 0)
|
||||||
console.print(f" [dim]queued actions: {queue_len} dispatched: {dispatched}[/]")
|
console.print(f" [dim]queued actions: {queue_len} dispatched: {dispatched}[/]")
|
||||||
|
|
||||||
# Surface repeated or rejected generations as overfitting diagnostics.
|
# Surface repeated or empty generations as overfitting diagnostics.
|
||||||
diag = getattr(runtime.policy_adapter, "diag", None)
|
diag = getattr(runtime.policy_adapter, "diag", None)
|
||||||
if diag is not None:
|
if diag is not None:
|
||||||
raw_subtask = diag.last_raw.get("subtask")
|
raw_subtask = diag.last_raw.get("subtask")
|
||||||
sub_rep = int(diag.repeat)
|
sub_rep = int(diag.repeat)
|
||||||
sub_gib = int(diag.gibberish.get("subtask", 0))
|
|
||||||
sub_empty = int(diag.empty.get("subtask", 0))
|
sub_empty = int(diag.empty.get("subtask", 0))
|
||||||
if raw_subtask is not None or sub_rep or sub_gib or sub_empty:
|
if raw_subtask is not None or sub_rep or sub_empty:
|
||||||
raw_display = (raw_subtask or "(empty)")[:80]
|
raw_display = (raw_subtask or "(empty)")[:80]
|
||||||
color = "yellow" if (sub_rep >= 3 or sub_gib >= 3 or sub_empty >= 3) else "dim"
|
color = "yellow" if (sub_rep >= 3 or sub_empty >= 3) else "dim"
|
||||||
console.print(
|
console.print(
|
||||||
f" [{color}]subtask diag repeat:{sub_rep} "
|
f" [{color}]subtask diag repeat:{sub_rep} empty:{sub_empty} "
|
||||||
f"gibberish:{sub_gib} empty:{sub_empty} "
|
|
||||||
f"last_raw: {raw_display!r}[/]"
|
f"last_raw: {raw_display!r}[/]"
|
||||||
)
|
)
|
||||||
mem_gib = int(diag.gibberish.get("memory", 0))
|
|
||||||
if mem_gib:
|
|
||||||
console.print(f" [dim]gen rejects memory:{mem_gib}[/]")
|
|
||||||
console.rule(style="cyan")
|
console.rule(style="cyan")
|
||||||
# Show recent generation warnings and speech oldest-first.
|
# Show recent generation warnings and speech oldest-first.
|
||||||
if scrollback:
|
if scrollback:
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from lerobot.runtime.adapter import (
|
|||||||
BaseLanguageAdapter,
|
BaseLanguageAdapter,
|
||||||
DirectTaskPolicyAdapter,
|
DirectTaskPolicyAdapter,
|
||||||
GenerationConfig,
|
GenerationConfig,
|
||||||
looks_like_gibberish,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -49,15 +48,15 @@ def test_cascade_sets_subtask_then_memory():
|
|||||||
assert adapter.calls == ["subtask", "memory"]
|
assert adapter.calls == ["subtask", "memory"]
|
||||||
|
|
||||||
|
|
||||||
def test_gibberish_subtask_is_rejected_and_counted():
|
def test_nonempty_generation_is_used_verbatim():
|
||||||
adapter = ScriptedAdapter({"subtask": [":::: ::"], "memory": ["should not run"]})
|
adapter = ScriptedAdapter({"subtask": [":::: ::"], "memory": ["memory"]})
|
||||||
state = RuntimeState(task="clean")
|
state = RuntimeState(task="clean")
|
||||||
|
|
||||||
adapter.update_language_state(None, state)
|
adapter.update_language_state(None, state)
|
||||||
|
|
||||||
assert "subtask" not in state.language_context
|
assert state.language_context["subtask"] == ":::: ::"
|
||||||
assert adapter.diag.gibberish.get("subtask") == 1
|
assert state.language_context["memory"] == "memory"
|
||||||
assert adapter.calls == ["subtask"] # memory never generated when subtask is rejected
|
assert adapter.calls == ["subtask", "memory"]
|
||||||
|
|
||||||
|
|
||||||
def test_throttle_regenerates_every_n_chunks():
|
def test_throttle_regenerates_every_n_chunks():
|
||||||
@@ -87,12 +86,6 @@ def test_handle_interjection_sets_plan_and_strips_say():
|
|||||||
assert state.language_context["plan"] == "turn to the left now"
|
assert state.language_context["plan"] == "turn to the left now"
|
||||||
|
|
||||||
|
|
||||||
def test_looks_like_gibberish_basic():
|
|
||||||
assert looks_like_gibberish("")
|
|
||||||
assert looks_like_gibberish(":::: ::")
|
|
||||||
assert not looks_like_gibberish("pick up the red cube")
|
|
||||||
|
|
||||||
|
|
||||||
def test_direct_task_adapter_delegates_action_chunk():
|
def test_direct_task_adapter_delegates_action_chunk():
|
||||||
class Policy:
|
class Policy:
|
||||||
def predict_action_chunk(self, observation):
|
def predict_action_chunk(self, observation):
|
||||||
|
|||||||
Reference in New Issue
Block a user