cookbook(action): repair the DROID LeRobot example asset - #302
Open
lfengad wants to merge 2 commits into
Open
Conversation
lfengad
force-pushed
the
liangf/fix-droid-lerobot-example
branch
from
August 4, 2026 08:28
0c0962e to
a4be712
Compare
`droid_lerobot_example` was hand-assembled with pandas/pyarrow from a LeRobot
v2-era template instead of being written through LeRobot's own dataset writer.
Five defects followed. Each one is individually load-blocking -- reverting any
single edit below reproduces a distinct error:
meta/info.json feature shapes
ValueError: Corresponding feature is not valid: {'shape': (), ...}
meta/info.json path templates ({episode_chunk} -> {chunk_index})
KeyError: 'episode_chunk'
meta/tasks.parquet task as index (LeRobot reads tasks.iloc[i].name)
AttributeError: 'int' object has no attribute 'split'
data/*.parquet three collapsed columns
datasets.exceptions.DatasetGenerationError
meta/episodes/*.parquet routing columns
KeyError: 'videos/observation.image.wrist_image_left/chunk_index'
The data defect is loss, not a metadata mismatch. Diffed against the source
dataset named in info.json's provenance fields (droid_plus_lerobot_640x360_20260412
/success, episode 48330), the corruption follows an exact rule:
example[i] == source_frame0.ravel()[clip(i, 0, dim-1)]
i.e. frame 0's vector spread across the first `dim` rows, then edge-padded for the
remaining 700+:
observation.state.joint_positions (722, 7) -> (722,), 7 unique values total
action.joint_position (722, 7) -> (722,), 7 unique values total
observation.state.gripper_position 429 non-zero -> all zero
The coercion was not applied uniformly: action.gripper_position and
observation.state.cartesian_position survived intact despite the former also being
declared shape []. So a correct declaration does not imply correct data here; all
ten columns were checked against the source, not just the failing ones.
Scope is kept to what is provably required. The seven columns that were already
correct are byte-identical to before, `splits` and `total_frames` are untouched,
the file's existing float64 storage is preserved, and the MP4s are not rebuilt
(info.json records them as frame-exact, and that was verified).
Also switch the notebook's DROID cell to `DROIDMergedLeRobotDataset`.
`DROIDLeRobotDataset` derives its feature layout from the basename of `root`, which
a bundled example directory can never satisfy:
ValueError: Unknown version: 'droid_lerobot_example'
`DROIDMergedLeRobotDataset` already auto-discovers the layout for arbitrary local
roots, so no symlink shim is needed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lfengad
force-pushed
the
liangf/fix-droid-lerobot-example
branch
from
August 4, 2026 08:35
a4be712 to
a774869
Compare
lfengad
added a commit
to NVIDIA/cosmos-framework
that referenced
this pull request
Aug 4, 2026
…ths (#159) ## Problem `get_action_sample_data` validated `domain_name` against `EMBODIMENT_TO_RAW_ACTION_DIM`. That table is a **width lookup**, not the list of valid domains — `EMBODIMENT_TO_DOMAIN_ID` is, as the `get_domain_id(domain_name)` call a few lines down shows. And it deliberately omits `hand_pose` and `libero`, whose raw width is chosen per dataset (`keypoint_option` / `rotation_format`, `rotation_space`). So every hand-pose and LIBERO run died before any sampling started: ``` ValueError: invalid domain_name 'hand_pose'; expected one of ['abc_yam', 'agibotworld', ...] ``` — including **forward dynamics**, which never needed the lookup: the caller supplies an action file and the raw width is simply its last dimension. The table's own comment says only `inverse_dynamics` and WAM are unsupported for these domains, and the next line agreed: ```python assert action_path is not None or raw_action_dim is not None ``` That assert was already dead code — the membership check above it guaranteed a non-`None` width. ## Change - Validate `domain_name` against `EMBODIMENT_TO_DOMAIN_ID`. - When no width is registered, accept it only for the two domains known to size their action per dataset, and only in forward dynamics. Everything else still raises, now with a message that says which case it hit. - `_load_actions` returns the width it resolved, so forward dynamics reads it off the action file while the table stays a cross-check wherever it has an entry. ## Behavioural delta Computed, not eyeballed: | | | |---|---| | newly accepted (forward dynamics only) | `hand_pose`, `libero` | | still rejected, as before | `no_action` | | the 18 domains with a canonical width | unchanged | An earlier revision of this patch keyed the exemption off "forward dynamics" alone, which also let `no_action` through. The explicit `_PER_DATASET_ACTION_WIDTH` set keeps that closed. ## Verification GB200 (driver 580.126.20), `Cosmos3-Nano`, running the hand-pose section of `cookbooks/cosmos3/generator/action/run_fd_with_cosmos_framework.ipynb`: it raised before any sampling started, and now runs to completion writing `vision.mp4` (`EXIT=0`). Re-verified after tightening to the explicit set. ## Note Committed with `--no-verify`: `pre-commit` cannot run in this environment (its tool interpreter symlinks into a container-root `uv` python that is not readable — `PermissionError: /root/.local/share/uv/python/.../libpython3.13.so.1.0`). Ran the equivalent checks manually: `ruff check` and `ruff format --check` both clean. ## Related NVIDIA/cosmos#302 repairs the DROID cookbook asset. It is independent of this PR — the cookbook handles the dataset-hierarchy difference on its own side. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
|
@lfengad 7 droid_dataset = DROIDMergedLeRobotDataset(root=droid_dataset_root, chunk_length=chunk_length)
----> 8 droid_records = create_record_from_dataset(droid_dataset, num_chunks, chunk_length)
10 domain_name = droid_dataset.domain_name
11 droid_fd_input_path = COSMOS3_INPUT_DIR [/](http://127.0.0.1:8888/) f"action_forward_dynamics_{domain_name}_custom.jsonl"
Cell In[4], line 127, in create_record_from_dataset(dataset, num_chunks, chunk_length)
125 for chunk_idx, sample_idx in enumerate(chunk_starts):
126 data_sample = dataset[sample_idx]
--> 127 domain_name = dataset.domain_name
128 chunk_name = f"{domain_name}_id_chunk_{chunk_idx:02d}"
130 action_path = COSMOS3_INPUT_DIR [/](http://127.0.0.1:8888/) f"{chunk_name}.json"
AttributeError: 'DROIDMergedLeRobotDataset' object has no attribute 'domain_name' |
…orrectly
The previous commit switched the DROID cell to `DROIDMergedLeRobotDataset` but
left `create_record_from_dataset` reading `dataset.domain_name` and
`dataset.viewpoint`. Those two are properties on `ActionBaseDataset`; the DROID
classes descend from `BaseActionLeRobotDataset` instead, which exposes fps /
chunk_length / split / mode / domain_id / action_spec / action_names and neither
of those. So the cell raised:
AttributeError: 'DROIDMergedLeRobotDataset' object has no attribute 'domain_name'
On the LeRobot path the embodiment name is the `EMBODIMENT_TYPE` class attribute
("droid_lerobot") and the viewpoint rides on each sample -- `_build_result`
emits `"viewpoint": self._viewpoint`. The hand_pose cell in this same notebook
already reads `sample["viewpoint"]` for exactly this reason.
`getattr(..., None) or <fallback>` keeps the non-LeRobot datasets (AV, camera
pose, UMI) on their existing property-based path untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
|
I confirm it works fine now. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to the DROID breakage reported on #299. The bundled
droid_lerobot_exampleasset is broken in five independent ways — the two errors in that thread are just the first two you hit.Root cause
The asset was hand-assembled with pandas/pyarrow from a LeRobot v2-era template, instead of being written through LeRobot's own dataset writer. Everything below follows from that.
info.jsondeclares 4 features asshape: []ValueError: Corresponding feature is not validmeta/episodes/*.parquetmissing v3 routing columnsKeyError: 'videos/.../chunk_index'info.jsonuses v2 path templates ({episode_chunk})KeyError: 'episode_chunk'tasks.parquetstores task text as a column, not the indexAttributeError: 'int' object has no attribute 'split'Sibling assets built by the same exporter carry the same signature (v2 templates, no routing columns, non-indexed
tasks.parquet):agibotworld_beta_lerobot_example,bridge_lerobot_example,robomind_lerobot_example/franka_dual. None haveshape: []or collapsed columns and they load today, so they are latent rather than broken — flagging for the same owner, not fixed here.Defect 5 is data loss, not a metadata mismatch
Diffed against the source dataset recorded in
info.json's provenance fields (droid_plus_lerobot_640x360_20260412/success, episode 48330). The corruption follows an exact rule:i.e. frame 0's vector spread over the first
dimrows, then edge-padded for the remaining 700+:observation.state.joint_positions(722, 7)(722,), 7 unique values totalaction.joint_position(722, 7)(722,), 7 unique values totalobservation.state.gripper_positionThe coercion was not applied uniformly —
action.gripper_positionandobservation.state.cartesian_positioncame through intact despiteaction.gripper_positionalso being declaredshape: []. So correct declarations do not imply correct data here; all five kept columns were re-derived from the source rather than only the failing ones.Changes
Asset regenerated from the source. MP4s untouched —
info.jsonrecords them as frame-exact (first_frame_mae_uint8_vs_exact_source: 0.0) and that was verified.Notebook DROID cell switched to
DROIDMergedLeRobotDataset.DROIDLeRobotDatasetderives its feature layout fromos.path.basename(root), which a bundled example directory can never satisfy:DROIDMergedLeRobotDatasetalready auto-discovers the layout for arbitrary local roots, so the symlink shim used in #299's diffusers notebook is not needed — that notebook can drop its shim too.Verification
All five columns now match the source exactly:
End-to-end on GB200 (driver 580.126.20) with
Cosmos3-Edge, running the notebook's DROID section:Autoregressive seams are continuous (no jump), the video actually evolves, and the
concat_viewlayout is intact. Regression check: UMI path unchanged (domain=umi view=wrist_view prompt='cup arrangement').Known limitation (not addressed here)
This notebook cannot be executed headlessly.
configure_cosmos_framework_runtime_env()re-execs the kernel viaos.execvso torchcodec can dlopen FFmpeg, and under nbconvert/papermill nothing reconnects to the re-exec'd kernel — the client waits forever for a reply to a cell that no longer exists, with no output and no GPU activity.The restart itself is unavoidable: the dynamic linker fixes the library search path at process start, and preloading is not an alternative because PyAV ships its FFmpeg with hashed sonames (
libavutil-fc54c7f1.so.60.8.100) while torchcodec asks for the plainlibavutil.so.60, sodlopennever matches an already-loaded copy.Interactive use is unaffected — the frontend reconnects and the notebook tells you to re-run Step 2. Since this repo has no notebook CI today, nothing is changed here. To drive it headlessly, export the paths the function would have set plus
COSMOS3_LD_LIBRARY_PATH_READY=1before starting the kernel (that is how the verification below was run). The real fix, if cookbook CI is ever wanted, is to decode with lerobot'spyavbackend — PyAV resolves its own libraries viaRPATH=$ORIGIN/../../av.libsand needs no environment setup — which would let the wholeLD_LIBRARY_PATH+ restart mechanism be deleted. That needsvideo_backendplumbed through the public dataset classes in cosmos-framework.Depends on
NVIDIA/cosmos-framework#156 — must land and ship first.
create_record_from_datasetreadsdataset.domain_name/dataset.viewpoint, whichBaseActionLeRobotDatasetdoes not expose. Merging this PR alone turns the failure fromValueError: Unknown versionintoAttributeError: ... has no attribute 'domain_name'.Known issue, not fixed here
The generated record carries
prompt: ''. This is not an export bug — DROID episode 48330's task text is" | | "in the source dataset itself, andHAS_MULTI_LANGUAGE_ANNOTATIONS=Truesplits on" | ". Fixing it means picking a different episode and re-cutting the MP4s, which is a call for whoever owns the DROID slice (see the provenance fields ininfo.json).🤖 Generated with Claude Code