File size: 7,996 Bytes
5d08972 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | # Turning VACE output into a trainable dataset β everything needed
After `run_aug32.sh` finishes you have per-episode videos. Training needs a LeRobot v2.1
"gr00t_views" dataset. This is the full inventory for doing that conversion on a machine
that has nothing but this repo.
Written 2026-09-19. The MimicGen side of the same job is documented in
`baseline/mimicgen/TRAINING_DATASET_PREP.md`; **the two are not the same procedure** β see
"Why VACE is the easy case" below.
---
## Why VACE is the easy case
VACE repaints pixels; it never changes the trajectory. So the augmented episode's
state/action rows are **the source episode's own rows, unchanged**. The builder copies that
parquet, rewrites indices and task ids, and links the new videos in. No simulator, no
re-render, no MuJoCo.
MimicGen is the opposite: every episode is a new rollout, so its parquet has to be rebuilt
from the generation HDF5 by replaying it in the simulator (~2 h for 256 episodes).
| | VACE | MimicGen |
|---|---|---|
| builder | `build_vace_objwise_gr00t_dataset.py` | `render_to_gr00t.py` |
| parquet | copied from the source episode | rebuilt from HDF5 |
| video | symlinked from the VACE output | re-rendered in MuJoCo |
| needs robosuite/robocasa/mujoco | **no** | yes |
| runtime for 256 eps | minutes | ~2 h |
---
## What to bring
### 1. The builder and its checker
```
train_robocasa/scripts/dataset_build/build_vace_objwise_gr00t_dataset.py
train_robocasa/scripts/dataset_build/repair_mimicgen_task_ids.py # also needed here
train_robocasa/scripts/dataset_build/verify_geom_objwise_gr00t_dataset.py
train_robocasa/scripts/dataset_build/merge_gr00t_view_datasets.py # only if merging with the 502 base
train_robocasa/scripts/dataset_build/verify_merged_gr00t_dataset.py
```
Only `numpy` and `pandas` beyond the standard library, plus `ffmpeg` on `PATH` for the
optional three-view preview (`--skip-three-view` avoids even that).
### 2. Inputs
| `--flag` | what | where it comes from |
|---|---|---|
| `--vace-root` | the VACE output tree, one directory per assignment holding `work_<view>/<inference_dir>/out_video.mp4` | `run_aug32.sh`'s work dir |
| `--manifest` | `assignments.jsonl` (repeatable) | `aug32/build_aug32_assignments.py` |
| `--source-root` | the original RoboCasa dataset β **the parquet is taken from here** | `datasets_pack/source_episodes.tar` |
| `--canonical-meta-source` | a dataset whose `info.json` / `modality.json` / `view_manifest.json` contract is copied | **`dataset_build/canonical_meta/`**, bundled here |
| `--output` | where the dataset is written | your choice |
The defaults all point at the original machine, so **pass all five explicitly**.
### The 502-episode gr00t_views set is NOT needed
Neither for training (we do not merge) nor for conversion:
- its `meta/` was the only thing `--canonical-meta-source` reads, and those six files are
bundled at `dataset_build/canonical_meta/` (96 KB);
- the state/action rows come from `--source-root`, i.e. `pickplace_target_human`, whose
parquet already carries **state 16-d and action 12-d** β the same schema as gr00t_views.
`--canonical-meta-source` expects a *dataset directory* and reads `<dir>/meta/`, so
`dataset_build/canonical_dataset/` is set up for exactly that β its `meta` is a symlink to
`canonical_meta`. Pass `--canonical-meta-source dataset_build/canonical_dataset`.
### 3. Manifest fields the builder reads
`case_dir`, `episode_index`, `provenance`, and the whole `source` block (episode, task,
`lang`, `source_mesh`, `source_category`, per-camera `videos`). `build_aug32_assignments.py`
writes all of them.
`provenance` decides what gets in:
```python
row.setdefault("provenance", "existing")
if row["provenance"] == "random_fill" and not include_random_fill:
continue # dropped
```
Our builder marks same-category assignments `existing` and the random remainder
`random_fill`. With 32 per target that is **59 same-category and 197 random**, so the
default keeps only 59. **Pass `--include-random-fill` to get all 256.**
---
## Run it
```bash
python build_vace_objwise_gr00t_dataset.py \
--vace-root /path/to/vace_aug32_work \
--manifest /path/to/vace_aug32_work/assignments.jsonl \
--source-root $PWD/data/pickplace_target_human \
--canonical-meta-source $PWD/dataset_build/canonical_dataset \
--output /path/to/gr00t_views/vace_per_target_32_256eps \
--include-random-fill \
--skip-three-view
python repair_mimicgen_task_ids.py --dataset /path/to/gr00t_views/vace_per_target_32_256eps
python verify_geom_objwise_gr00t_dataset.py --dataset /path/to/gr00t_views/vace_per_target_32_256eps
```
`--copy-videos` materialises the videos instead of symlinking β needed if the dataset will
be moved away from the VACE output tree.
### `repair_mimicgen_task_ids.py` is not optional
Its docstring: the writer "stores all parquet task columns as 0 while episodes.jsonl stores
the intended language per episode", and training reads the task id from the parquet β so
without this pass **every augmented episode trains as task 0** and the language conditioning
silently collapses. It also renames the modality keys to the canonical
`left_view` / `right_view` / `wrist_view` that `single_panda_gripper` expects.
---
## What the result must look like
```
<output>/
meta/{info,modality,episodes,tasks,stats}.json all five, asserted by the loader
data/chunk-000/episode_NNNNNN.parquet
videos/chunk-000/observation.images.robot0_agentview_left/episode_NNNNNN.mp4
.../robot0_agentview_right/... .../robot0_eye_in_hand/...
extras/episode_NNNNNN/... eval only, training never reads it
```
parquet columns (11): `observation.state` (list, **16**), `action` (list, **12**),
`next.reward`, `next.done`, `timestamp`, `frame_index`, `episode_index`, `index`,
`task_index`, `annotation.human.task_description` (**int index into tasks.jsonl**, not the
string), `annotation.human.task_name`.
`modality.json` slicing:
```
state base_position 0:3 | base_rotation 3:7 (quat) | EE_pos_rel 7:10
| EE_rot_rel 10:14 (quat) | gripper_qpos 14:16
action base_motion 0:4 | control_mode 4:5 | EE_pos 5:8 | EE_rot 8:11 | gripper_close 11:12
video left_view / right_view / wrist_view -> observation.images.robot0_agentview_{left,right}
and observation.images.robot0_eye_in_hand
```
---
## Then training
`train_robocasa/` is a separate self-contained bundle (49 MB + the `mygr00t` conda env and a
7.1 GB base checkpoint, both referenced by absolute path). Point it at the dataset by
absolute path rather than by name:
```bash
cd train_robocasa
DATASET_PATH=/path/to/gr00t_views/vace_per_target_32_256eps \
GPUS=0,1,2,3 MAX_STEPS=30000 SAVE_STEPS=5000 PER_GPU_BATCH=16 \
bash scripts/train_groot15_single_dataset.sh
```
`DATASETNAME` (the alternative) resolves against
`/lp-dev/jonghoon/robocasa_full/gr00t_views/`, which will not exist elsewhere.
Check first with `bash scripts/smoke_test.sh <gpu>` β imports, dataset load, and a real
20-step fine-tune.
---
## Checklist for the other machine
- [ ] `run_aug32.sh` finished; every assignment has three `out_video.mp4`
- [ ] `assignments.jsonl` carries `provenance` (re-run `build_aug32_assignments.py` if it predates 2026-09-19)
- [ ] `source_episodes.tar` unpacked β **the parquet (`data/`) is read from it**; the archive
was repacked 2026-09-19 to include it, an earlier copy has only `videos/` and `extras/`
- [ ] `dataset_build/canonical_dataset/` present (its `meta` symlink must resolve; use
`cp -rL` when moving)
- [ ] `build_vace_objwise_gr00t_dataset.py` + `repair_mimicgen_task_ids.py` + a verifier copied over
- [ ] python with `numpy`, `pandas`; `ffmpeg` unless `--skip-three-view`
- [ ] decided: `--include-random-fill` (256) or not (59)
- [ ] decided: merge with the 502 base or train on the augmented set alone
|