mirror of
https://github.com/huggingface/lerobot.git
synced 2026-08-08 17:39:44 +00:00
fix(datasets): Resolve only recipe-referenced bindings
Eagerly resolving every DEFAULT_BINDINGS entry made rendering fail on frames whose events a default binding cannot disambiguate, e.g. the camera-less vqa default on multi-camera frames, even when the recipe never references that binding. Add TrainingRecipe.referenced_binding_names() and skip bindings the recipe does not consume. Split out of #4183 so the data-layer fix lands independently of the language runtime. Refs #4183 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -192,6 +192,13 @@ class TrainingRecipe:
|
||||
if recipe.weight <= 0:
|
||||
raise ValueError(f"Blend component {name!r} must have a positive weight.")
|
||||
|
||||
def referenced_binding_names(self) -> set[str]:
|
||||
"""Names of every binding referenced by this recipe's message turns."""
|
||||
names: set[str] = set()
|
||||
for turn in self.messages or []:
|
||||
names |= self._referenced_bindings(turn)
|
||||
return names
|
||||
|
||||
def _referenced_bindings(self, turn: MessageTurn) -> set[str]:
|
||||
"""Return the binding names that ``turn`` references via placeholders or attributes."""
|
||||
names: set[str] = set()
|
||||
|
||||
@@ -290,8 +290,14 @@ def _resolve_bindings(
|
||||
bindings: dict[str, LanguageRow | str | None] = {
|
||||
"task": _resolve_task(task, dataset_ctx, persistent=persistent, sample_idx=sample_idx),
|
||||
}
|
||||
specs = {**DEFAULT_BINDINGS, **(recipe.bindings or {})}
|
||||
declared = recipe.bindings or {}
|
||||
specs = {**DEFAULT_BINDINGS, **declared}
|
||||
# Only resolve bindings the recipe consumes: an unreferenced default may be
|
||||
# unresolvable, e.g. the camera-less ``vqa`` default on multi-camera frames.
|
||||
needed = recipe.referenced_binding_names() | set(declared)
|
||||
for name, spec in specs.items():
|
||||
if name not in needed:
|
||||
continue
|
||||
bindings[name] = _resolve_spec(spec, persistent=persistent, events=events, t=t)
|
||||
return bindings
|
||||
|
||||
|
||||
@@ -197,6 +197,34 @@ def test_emitted_at_filters_vqa_by_camera():
|
||||
assert wrist["content"] == '{"count": 1}'
|
||||
|
||||
|
||||
def test_unreferenced_default_bindings_are_not_resolved():
|
||||
# A recipe that never references ``vqa`` must render on frames carrying
|
||||
# multi-camera VQA events, which the camera-less default ``vqa`` binding
|
||||
# cannot disambiguate (regression: eager DEFAULT_BINDINGS resolution).
|
||||
recipe = TrainingRecipe(
|
||||
messages=[
|
||||
MessageTurn(role="user", content="${task}", stream="low_level"),
|
||||
MessageTurn(
|
||||
role="assistant",
|
||||
content="${subtask}",
|
||||
stream="low_level",
|
||||
target=True,
|
||||
if_present="subtask",
|
||||
),
|
||||
]
|
||||
)
|
||||
rendered = render_sample(
|
||||
recipe=recipe,
|
||||
persistent=PERSISTENT,
|
||||
events=EVENTS_AT_3_TWO_CAMERAS,
|
||||
t=3.0,
|
||||
sample_idx=0,
|
||||
task="tidy the table",
|
||||
)
|
||||
assert rendered is not None
|
||||
assert rendered["messages"][1]["content"] == "subtask 1"
|
||||
|
||||
|
||||
def test_emitted_at_raises_on_ambiguous_per_camera_vqa():
|
||||
with pytest.raises(ValueError, match="Ambiguous resolver"):
|
||||
emitted_at(
|
||||
|
||||
Reference in New Issue
Block a user