mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-06 17:41:47 +00:00
4fa9578e3d
Cleanup pass over the language-support PR to cut LOC and scope creep. Removals: - SayTool + tools/ package (registry, Tool protocol, [tools] extra) and the runtime's tool-dispatch path. Kept <say> training supervision and inference stripping so speech-annotated datasets still train. - WeightedEpisodeAwareSampler + VQA oversampling wiring (_build_vqa_oversample_weights, vqa_target_fraction) — training uses plain EpisodeAwareSampler again. - Debug env-gates PI052_DEBUG_TENSORS, PI052_SUBTASK_USE_TASK, EVAL_TASK_OVERRIDE. - Dead code: broken _tp._DUMP_BUDGET block, unused imports (copy/Tensor, RevisionNotFoundError, LeRobotDataset, os), messages_for_vqa, steps.py shim (modeling imports pi052_adapter directly), duplicated _emit, builtins.type[T]. Moves: - Policy-agnostic runtime -> src/lerobot/runtime/ (LanguageConditionedRuntime + adapter Protocol + state); pi052 keeps only its adapter + CLI. Tests -> tests/runtime/. Other: - Compacted verbose AI-authored comments/docstrings across pi052 (kept the hard-won DDP / barrier-timeout / reduce-max / VQA-routing notes). - Relocated LM-head prediction debug helper to pi052/debug_utils.py. - Fixed test_render_messages: assert task-fallback render (current behavior) instead of the stale no-op expectation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
148 lines
5.0 KiB
Python
148 lines
5.0 KiB
Python
#!/usr/bin/env python
|
|
|
|
import pytest
|
|
|
|
pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")
|
|
|
|
import torch # noqa: E402
|
|
|
|
from lerobot.configs.recipe import MessageTurn, TrainingRecipe # noqa: E402
|
|
from lerobot.processor.converters import create_transition # noqa: E402
|
|
from lerobot.processor.render_messages_processor import RenderMessagesStep # noqa: E402
|
|
from lerobot.types import TransitionKey # noqa: E402
|
|
|
|
|
|
def test_render_messages_step_renders_task_fallback_without_language_columns():
|
|
"""No language columns + a task string → low-level task fallback render,
|
|
matching what the policy sees at eval time on unannotated observations."""
|
|
recipe = TrainingRecipe(
|
|
messages=[
|
|
MessageTurn(role="user", content="${task}", stream="high_level"),
|
|
MessageTurn(role="assistant", content="${subtask}", stream="low_level", target=True),
|
|
]
|
|
)
|
|
transition = create_transition(complementary_data={"task": "do it"})
|
|
|
|
out = RenderMessagesStep(recipe)(transition)
|
|
data = out[TransitionKey.COMPLEMENTARY_DATA]
|
|
|
|
assert data["messages"] == [{"role": "user", "content": "do it"}]
|
|
assert data["message_streams"] == ["low_level"]
|
|
assert data["target_message_indices"] == []
|
|
assert data["task"] == "do it"
|
|
|
|
|
|
def test_render_messages_step_noops_without_language_columns_or_task():
|
|
recipe = TrainingRecipe(
|
|
messages=[
|
|
MessageTurn(role="user", content="${task}", stream="high_level"),
|
|
MessageTurn(role="assistant", content="${subtask}", stream="low_level", target=True),
|
|
]
|
|
)
|
|
transition = create_transition(complementary_data={})
|
|
|
|
assert RenderMessagesStep(recipe)(transition) == transition
|
|
|
|
|
|
def test_render_messages_step_renders_and_drops_raw_language():
|
|
recipe = TrainingRecipe(
|
|
messages=[
|
|
MessageTurn(role="user", content="${task}", stream="high_level"),
|
|
MessageTurn(role="assistant", content="${subtask}", stream="low_level", target=True),
|
|
]
|
|
)
|
|
transition = create_transition(
|
|
complementary_data={
|
|
"task": "do it",
|
|
"timestamp": torch.tensor(0.0),
|
|
"index": torch.tensor(7),
|
|
"language_persistent": [
|
|
{
|
|
"role": "assistant",
|
|
"content": "reach carefully",
|
|
"style": "subtask",
|
|
"timestamp": 0.0,
|
|
"camera": None,
|
|
"tool_calls": None,
|
|
}
|
|
],
|
|
"language_events": [],
|
|
}
|
|
)
|
|
|
|
out = RenderMessagesStep(recipe)(transition)
|
|
data = out[TransitionKey.COMPLEMENTARY_DATA]
|
|
|
|
assert "language_persistent" not in data
|
|
assert "language_events" not in data
|
|
assert data["messages"][-1]["content"] == "reach carefully"
|
|
assert data["message_streams"] == ["high_level", "low_level"]
|
|
assert data["target_message_indices"] == [1]
|
|
|
|
|
|
def test_render_messages_step_falls_back_to_low_level_task_when_recipe_misses():
|
|
recipe = TrainingRecipe(
|
|
messages=[
|
|
MessageTurn(
|
|
role="assistant",
|
|
content="${subtask}",
|
|
stream="high_level",
|
|
target=True,
|
|
if_present="subtask",
|
|
),
|
|
]
|
|
)
|
|
transition = create_transition(
|
|
complementary_data={
|
|
"task": "pick the cube",
|
|
"timestamp": torch.tensor(0.0),
|
|
"index": torch.tensor(7),
|
|
"language_persistent": [],
|
|
"language_events": [{"style": "unmatched", "timestamp": 0.0}],
|
|
}
|
|
)
|
|
|
|
out = RenderMessagesStep(recipe)(transition)
|
|
data = out[TransitionKey.COMPLEMENTARY_DATA]
|
|
|
|
assert data["messages"] == [{"role": "user", "content": "pick the cube"}]
|
|
assert data["message_streams"] == ["low_level"]
|
|
assert data["target_message_indices"] == []
|
|
|
|
|
|
def test_render_messages_step_falls_back_per_sample_in_batched_language():
|
|
recipe = TrainingRecipe(
|
|
messages=[
|
|
MessageTurn(
|
|
role="assistant",
|
|
content="${subtask}",
|
|
stream="high_level",
|
|
target=True,
|
|
if_present="subtask",
|
|
),
|
|
]
|
|
)
|
|
transition = create_transition(
|
|
action=torch.arange(4).reshape(2, 2),
|
|
complementary_data={
|
|
"task": ["pick the cube", "open the drawer"],
|
|
"timestamp": torch.tensor([0.0, 1.0]),
|
|
"index": torch.tensor([7, 8]),
|
|
"language_persistent": [[], []],
|
|
"language_events": [
|
|
[{"style": "unmatched", "timestamp": 0.0}],
|
|
[{"style": "unmatched", "timestamp": 1.0}],
|
|
],
|
|
},
|
|
)
|
|
|
|
out = RenderMessagesStep(recipe)(transition)
|
|
data = out[TransitionKey.COMPLEMENTARY_DATA]
|
|
|
|
assert data["messages"] == [
|
|
[{"role": "user", "content": "pick the cube"}],
|
|
[{"role": "user", "content": "open the drawer"}],
|
|
]
|
|
assert data["message_streams"] == [["low_level"], ["low_level"]]
|
|
assert data["target_message_indices"] == [[], []]
|