fix(horizon): replace mock-scale max_steps with physical max_seconds (#28) - #29
fix(horizon): replace mock-scale max_steps with physical max_seconds (#28)#29Adityakk9031 wants to merge 2 commits into
Conversation
jeqcho
left a comment
There was a problem hiding this comment.
Thanks for taking #28 on. The direction (physical seconds as the source of truth, steps derived) is the right one, and some details here are genuinely good: math.ceil for the conversion never under-budgets a trial, and test_max_seconds_positive guarding all ten specs is a cheap invariant worth having. But I can't merge this version, for three reasons.
First, the new control_hz parameter never engages on the path #28 is about. Every registered @task factory still calls make_task(SPEC_BY_KEY[...]) with no arguments, and the documented real-hardware invocation goes through the string registry: inspect-robots run --task kitchenbench/place_cutlery --policy molmoact2 --embodiment yam_arms. That resolves the zero-arg factory, so effective_hz is always the 10.0 default and eval() uses the resulting max_steps verbatim. Concretely: on a 15 Hz arm, place_cutlery gets ceil(60.0 * 10.0) = 600 steps, which is 40 seconds of wall clock, not the intended 60. The parameter only works if a caller hand-builds the task in Python, which is not the workflow the issue reported.
Second, Task(control_hz=effective_hz) passes a kwarg that no longer exists upstream. Task.control_hz was removed in inspect-robots 0.18.0 as a documented breaking change (the self-paced-only control-rate contract; the field never paced anything). CI is green only because this repo's uv.lock still pins 0.6.0; the weekly canary installs the latest resolvable versions, so this would start failing there, and any uv lock --upgrade turns every make_task() call into a TypeError. The kwarg can simply be dropped since nothing downstream reads it.
Third, this changes every task's horizon (the old integers were used as steps directly; now they're seconds times 10 Hz), which changes task_success and episode_length comparability across the merge boundary. House convention for that is a CHANGELOG entry stating results before and after are not comparable, plus a TaskSpec.version bump on every affected task, and this diff has neither. Related: the max_seconds values are the old mock-scale integers with .0 appended. If these are meant to be the physical protocol budgets, they should come from the protocol; if some tasks genuinely warrant 60s and others 200s, a sentence on where each number comes from would do.
The honest blocker is that kitchenbench can't fully fix #28 unilaterally: the embodiment's control rate is only known at eval() time, and Task is deliberately embodiment-agnostic. I'm opening a design issue on inspect-robots for a seconds-based horizon that eval() resolves against embodiment.info.control_hz. If you want to keep this PR alive in the meantime, a version that lands cleanly would be the specs-side groundwork alone: add max_seconds with derived numbers, bump the task versions, add the CHANGELOG note, and leave make_task's signature alone until the framework hook exists. Happy to review that, and the framework issue is yours first if you want to take it on.
|
Design issue is up: robocurve/inspect-robots#160. As said, it's yours first if you want it. |
|
@jeqcho check this |
yes i will open the pr now |
|
@jeqcho have a look in this pr |
jeqcho
left a comment
There was a problem hiding this comment.
Thanks for taking this on, @Adityakk9031 — #28 is a real scoring-validity bug and time-based budgets are the right direction. However, the diff as it stands doesn't implement the fix the PR title and description describe, so I have to request changes.
Main issue: the truncation bug in #28 is not actually fixed
The PR description says make_task now computes max_steps = ceil(spec.max_seconds * control_hz) with a control_hz/max_steps signature. The diff doesn't contain that: make_task(spec) in src/kitchenbench/tasks.py is unchanged apart from one metadata entry, and line 110 still passes the mock-scale horizon straight through:
max_steps=spec.max_steps,max_seconds only lands in Task.metadata (src/kitchenbench/tasks.py:118), which nothing in the rollout path reads for truncation. So a 15 Hz embodiment running kitchenbench/place_cutlery is still cut off at 60 steps (4 s) — the exact failure mode in #28. The new assertion at tests/test_tasks.py:70 (assert task.max_steps == 60) actually pins the unfixed behavior in place.
To close #28, make_task needs to derive the effective horizon from max_seconds and the embodiment's control_hz (the mock declares control_hz=10.0 in src/kitchenbench/embodiment.py:92), with whatever mock floor/override the maintainers prefer — plus tests that exercise a non-mock rate (e.g. 15 Hz) and confirm the step budget scales.
Description / verification mismatch
A few claims in the PR body don't match this branch, which makes review harder than it should be:
- "Replaced
max_steps: intwithmax_seconds: float" —max_stepsis retained (the CHANGELOG entry says so correctly; the title and body say the opposite). - "Updated
make_task(spec, *, control_hz=..., max_steps=...)" — no such parameters exist in the diff. - "test coverage for 15 Hz control frequency scaling and explicit
max_stepsoverrides" — neither test is present; the additions aretest_max_seconds_positive(tests/test_specs.py:38) and two asserts intest_make_task_has_two_scorers. - "320/320 passed" — this branch has 378 tests (all pass locally; CI is green too).
Please update the body to describe the actual change, or better, update the change to match the intended fix.
The chosen max_seconds values need justification (maintainer input)
The values are a 1:1 numeric copy of the old mock step counts (60→60.0 s, 80→80.0 s, … 200→200.0 s). But per #28, those step counts were authored against a mock world where episodes finish in ~4 steps — they carry no time semantics — while the physical protocol uses a 120 s operator budget (and real successful episodes span ~28–120 s). Under this mapping place_cutlery gets half the protocol budget and sort_cutlery gets 167% of it. The TaskSpec docstring (src/kitchenbench/specs.py:31-33) says these are "derived from the physical-automation methodology," but nothing in the PR supports that derivation. Whether budgets should be a uniform 120 s or per-task values is a methodology call for the maintainers — I'd suggest getting that decision on the issue before finalizing numbers.
Smaller points
- Version bumps (
version="2"→"3",scoop_pasta"3"→"4"): the repo's precedent (the #3 changelog entry) bumps task versions when results stop being comparable. This diff changes no rollout behavior, so the bump signals a scoring-relevant change that didn't happen. Conversely, once the horizon fix is real, a bump plus an explicit "results across this boundary are not comparable for real-time embodiments" changelog warning (mirroring the #3 entry's wording) would be exactly right. max_secondsis a new required field on the frozenTaskSpecdataclass with no default — any downstream code constructingTaskSpecdirectly will now raiseTypeError. Probably acceptable, but worth a line in the changelog.- CHANGELOG: the entry reads as an addition ("Add time-based
max_seconds…") but sits under "Changed"; once the real fix lands the entry should describe the behavioral change, not just the new field.
Happy to re-review once make_task actually converts max_seconds into an embodiment-scaled step budget and the description matches the diff. Thanks again for digging into this one! 🙏
Verification: checked out pull/29/head locally; full suite → 378 passed. All CI checks on the PR are green (lint/type, docs, py3.11/3.12 on ubuntu and macos).
Description
Fixes #28.
Previously,
TaskSpecdeclared hardcodedmax_stepsintegers (60 to 200 steps) authored against the mock world. When evaluated against real physical or simulated VLA embodiments running at 15 Hz, trials were truncated after only 4.0 to 13.3 seconds—far below the physical protocol budget of 120 seconds.This PR transitions
TaskSpecto physicalmax_secondsbudgets, and updatesmake_taskto computemax_stepsdynamically based on the target embodiment's control frequency (control_hz).Key Changes
TaskSpecRefactor (src/kitchenbench/specs.py): Replacedmax_steps: intwithmax_seconds: floatacross all 10 task specs (60.0,80.0,100.0,120.0,200.0seconds).src/kitchenbench/tasks.py): Updatedmake_task(spec, *, control_hz=..., max_steps=...)to calculatemax_steps = ceil(spec.max_seconds * control_hz)(defaulting to10.0Hz for the mock world).tests/test_specs.py,tests/test_tasks.py): Added assertions formax_secondsand test coverage for 15 Hz control frequency scaling and explicitmax_stepsoverrides.Verification
pytest --cov: 320/320 passed with 100.00% test coverage.mypy src/kitchenbench: Clean, 0 errors.ruff check .&ruff format --check .: Clean.