File size: 15,893 Bytes
f6bd379 dce0041 f6bd379 dce0041 f6bd379 dce0041 f6bd379 3f09056 f6bd379 3f09056 f6bd379 3f09056 f6bd379 3f09056 f6bd379 | 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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 | # Handoff: the `press_button` arm on a Jetstream2 g3.xl
Written 2026-11-10 by the workstation session for whoever picks this up on Jetstream2. Everything below that is a
measurement was measured; everything that is a guess says so. Read the "unverified" notes before trusting a number.
## 0. The repository
```bash
git clone -b newton https://github.com/DingTianxingjian/Latent_Free.git latent_free && cd latent_free
```
**It is public -- no token, no credentials.** (The first reader of this document guessed `Potestates/Latent_Free`
from the HuggingFace org name and got a 404; the owner is `DingTianxingjian`.)
## 0b. Read these first, in this order
1. `AGENTS.md` (repo root) -- **the experiment ledger is `LEDGER.md` at the repo root, and it must be updated before
you report a result to the user.** Configuration, split, budget, the number, its control, conclusion status,
artifact paths, next decision. Do not leave a conclusion only in chat or in a subdirectory README.
2. `LEDGER.md`, the entry **2026-11-10 "FIRST PHYSICAL-ROBOT feedback on the Piper policy"**. It has everything
already measured about this robot and policy family. Do not re-measure it.
3. `next_task.md` section 8: **never discretise our latent for the VLA; never change a shared setting for one arm
only.** The second one is the live constraint here -- see section 6.
## 1. What this run is for
`anhdao69/press_button_task` is the **first multi-task real-robot dataset in this project**. The pick-can set has
exactly one task, which means the instruction carried **zero information**: the policy could ignore the language
entirely and nothing would change. The press-button set contains at least `"press the red button"` and
`"press the blue button"` -- same scene, same motion class, the only disambiguator is the word. **That is the first
clean test of whether this policy family reads its instruction at all**, and it is worth more than a second task
would be on its own. Register that as the question before you train, not after.
## 2. The machine: what fits on a g3.xl
| | |
|---|---|
| g3.xl | 32 vCPU, **120 GB system RAM**, **40 GB GPU RAM** (a full A100), 64 SU/h |
| smaller flavors | g3.large 20 GB GPU / 60 GB RAM; g3.medium 10 GB / 30 GB. **Neither is enough.** |
| multi-GPU | g3.2xl / g3.4xl exist but are **not available by default** -- a justified request to help@jetstream-cloud.org |
**Measured on the workstation (RTX PRO 6000, 2026-11-10): training takes 39.9-40.8 GB of VRAM at micro-batch 8.**
That does not fit in 40 GB with any headroom. So:
```
BS=4 GA=16 estimated ~28 GB <- use this
BS=8 GA=8 40 GB measured <- will OOM on an A100
```
**The global batch must stay 64** (`BS x GA x GPUs = 64`); the launcher asserts it. Every Piper and 10 % LIBERO cell
in this project is 64, and changing it makes the run incomparable with all of them. The ~28 GB figure is an
extrapolation from one measured point (fixed ~16 GB + activations linear in batch), **not measured** -- check
`nvidia-smi` at step 50 and drop to BS=2 GA=32 if it is close to the limit.
Other facts worth having before you start:
* **Disk: plan 80-100 GB and attach a volume.** `openvla-7b` is 15 GB, the env is ~11 GB, each merged checkpoint is
16 GB, the phase-2 base (if you get it) is another 15 GB.
* **120 GB RAM is the one place Jetstream2 beats the workstation.** The standalone merge peaks at **30.7 GB** and was
SIGKILLed three times on the 62 GB workstation (LEDGER 2026-11-08). On g3.xl it is a non-issue.
* **Measure the first 100 steps before quoting an ETA to anyone.** On 2026-11-10 the workstation estimate of 13 h
became 18 h because another user's job was on the same card. `train_log.jsonl` has a `seconds` field; use it.
## 3. Environment
Follow **`REPRODUCE.md`, the `univla_vla` recipe** (torch 2.2.0 + cu121, `pip install -e third_party/UniVLA`, the
pinned `tensorflow_metadata` / `protobuf` / `wandb`). That recipe targets sm80, which **is** the A100 -- do not copy
this workstation's env, which is torch 2.7.1+cu128 built for Blackwell.
Two things that save time:
* **Training never asks for flash-attention.** Grep confirms `attn_implementation` appears only in the two inference
scripts (`run_libero_eval_cont.py`, `realrobot/piper_policy.py`) and both use `"sdpa"`. So you do not need to build
flash-attn for sm80.
* **Conversion needs `pyarrow` and `av`** on top of the env (`cv2` too, which the env has). The workstation does not
have them, which is why this dataset was not converted there.
## 4. The dataset, and three things that will bite you
Verified from `meta/info.json` and `meta/stats.json` (read without downloading the videos):
```
robot_type piper | LeRobot codebase_version v3.0 | fps 30
total_episodes 100 | total_frames 5533 | total_tasks 4
action float32 [7] joint_1.pos .. joint_6.pos + gripper (ABSOLUTE joint positions, degrees)
observation.state float32 [14]
observation.images.front / .wrist video 480x640x3 mp4
```
Same robot, same action space and same layout as `pick_can_place_plate_150_trimmed`, so
`acdc_la/realrobot/convert_piper_rlds.py` reads it **without a code change** to its parsing. But:
### (a) `GRIP_SCALE = 8.2` is WRONG for this dataset
`convert_piper_rlds.py:20` divides the raw gripper by 8.2 because the pick-can set's raw gripper ranges 0 .. 8.15.
Here it does not:
```
action dim 6: min 0.04 max 0.06 mean 0.05 std 0.01
```
Pressing a button never opens the gripper. `clip(act[:,6] / 8.2, 0, 1)` maps all of it to ~0.005 -- a **constant**.
Numerically harmless, but that dimension carries no signal, and if the two datasets are ever trained jointly the
gripper calibrations are incompatible. **Make a per-dataset decision and write it in the converter's docstring.**
### (b) The episodes are very short, and a 12-step chunk may not fit in them
```
5533 frames / 30 Hz, subsampled by 3 -> ~1,844 steps at 10 Hz (pick-can: 6,462, i.e. 3.5x more)
1,844 / 100 episodes -> ~18 steps = 1.8 s per episode
window_size 12 = 1.2 s -> two thirds of an entire episode
RLDSDataset is built with window_size + 2 = 14 frames per sample -> ~5 valid windows per episode
```
**SETTLED 2026-11-10 -- it pads, nothing masks it, and for this dataset that is most of your samples.** Measured by
running the real chunker on synthetic trajectories (`acdc_la/realrobot/short_episode.py`):
* The window is **backward-looking**: timestep `t` gets indices `[t-13 ... t]`, then
`tf.maximum(chunk_indices, 0)` (`traj_transforms.py:91`) clamps anything before the start, so **frame 0 and
action 0 are repeated**. Actions are clamped the same way to `[0, traj_len-1]` (`:98`).
* **No sample is dropped** -- one per timestep, including `t=0`, whose entire 14-frame window is frame 0 and whose
entire action target is action 0. A fully degenerate sample.
* `pad_mask` records exactly which entries are padding (`:104`) and **nothing downstream reads it**: a grep over
`prismatic/vla/` and `acdc_la/` finds it used only by `acdc_la/scripts/test_obs_keep.py`. (The `pad_mask_dict`
that *is* used is a different thing -- missing images and language.)
* Fraction of samples with a fully real window, by episode length in 10 Hz steps:
| episode length | 12 (press_button shortest) | 17 (press_button median) | 43 (pick-can median) | ~203 (LIBERO Long) |
|---|---|---|---|---|
| clean samples | **0 %** | **24 %** | 70 % | 94 % |
So **press_button at 10 Hz is mostly padding** and its shortest episodes yield no clean sample at all. Compute the
exact fraction on your copy as `sum(max(0, T_i - 13)) / sum(T_i)` over episodes and put it in the ledger entry; the
effective sample count is what that gives, not 1,844.
**Do not "fix" this by lowering `window_size`** -- it is shared with every LIBERO cell and with the baseline
(`next_task.md` section 8). The options, in the order I would raise them with the user:
1. **Accept it and report it.** The contamination is identical on both sides of every arm, so an A-vs-baseline
comparison on this dataset stays valid; only the absolute number stops being comparable with pick-can.
2. **Keep 30 Hz for this dataset instead of subsampling by 3.** Episodes are 38-167 frames, so a 14-frame window
leaves 25-154 clean samples -- most samples clean. But it changes the control rate and what a 12-step chunk
spans (0.4 s instead of 1.2 s), so it is a design decision, not a fix. Raise it; do not take it alone.
3. `chunk_filter_fn` exists in the dataset API (`dataset.py:411-420`) and could drop padded chunks, but that changes
the effective data for one arm.
`prismatic/vla/datasets/rlds/traj_transforms.py::chunk_act_obs_libero` is the function; the test is one file.
### (c) Overfitting is a real risk here -- unlike on pick-can
```
1,844 steps of data / global batch 64 -> 29 steps per epoch
10,000 steps -> 347 epochs (pick-can at 10k was 104)
95/5 split of 100 episodes -> 95 train, only 5 held out
```
The collaborator's "is it under- or over-trained?" question was a **negative** for pick-can (held-out L1 flat from
step 6,000 to 10,000 while train L1 still fell). **On this dataset it will genuinely bite.** So:
* Pick a smaller budget. ~3,000 steps is ~104 epochs, matching pick-can's epoch count, which is the defensible
choice -- not 10,000.
* **Actually watch the held-out curve this time**, and snapshot checkpoints: `finetune_libero_ctrl.py:496` writes
every save to the SAME adapter dir, so each save overwrites the last and the run keeps **no history**. Copy
`<run_root>/adapters/<exp_id>` after each save or you cannot pick a step afterwards.
* The 4 tasks are **blue / orange / pink / red button, 25 episodes each** (confirmed on the real download
2026-11-10). Four colours of the same motion is a *better* language test than I assumed from two.
## 5. Registering the dataset
`convert_piper_rlds.py` builds a TFDS builder whose **dataset name comes from the class name** (`PiperPickCan` ->
`piper_pick_can`), and `piper_pick_can` is registered in prismatic by `acdc_la/patches/univla_piper.patch` (image
keys + the `EEF_POS` contract). For a new dataset, copy the converter to `convert_press_button_rlds.py`, rename the
builder class to `PressButton`, and add a `press_button` entry to the patch's two tables (`OXE_DATASET_CONFIGS` in
`oxe/configs.py` and `OXE_STANDARDIZATION_TRANSFORMS` in `oxe/transforms.py`) -- the Piper entries can be copied
verbatim, both datasets have the same image keys and action contract.
**A gotcha I already hit:** TFDS refuses to build a builder class whose module is `__main__`
("Cannot access resources for '__main__'"). Run it as an import, not as a script:
```bash
PYTHONPATH=.:third_party/UniVLA python -c "
import sys; sys.argv = ['x', '--src', '<lerobot dir>', '--out', 'data/real_rlds']
from acdc_la.realrobot.convert_press_button_rlds import main; main()"
```
The shortcut of reusing the `piper_pick_can` registration under a different `--data_root_dir` works (that is what the
delta copy does) but gives both datasets the same key in `dataset_statistics.json`, which is a real footgun. Register
a proper name.
## 6. Absolute or delta actions? Do not decide this alone
Two 10,000-step runs are in flight on the workstation **right now** testing exactly this on pick-can, both on delta
actions at global batch 64 (expected to finish ~2026-11-10 22:00 UTC):
* `tc_cont+piper_pick_can+b64x1+...--terra_piper_delta` -- our phase-2 base
* `none+piper_pick_can+b64x1+...--openvla_piper_delta` -- official openvla-7b, no latent mechanism (the baseline)
Why: the absolute parameterisation spends a 164 deg output range on a ~2 deg/step signal (1.2 %), against a measured
error of 2.4-11.5 % of each joint's range -- the signal sits below the error (LEDGER 2026-11-10). The delta is
`action[:6] - joint_state[:6]`, target minus the step's own **measured** configuration, which is self-correcting at
deployment; `acdc_la/realrobot/make_delta_rlds.py` derives a delta RLDS from an absolute one in ~3 minutes with the
frames bit-identical.
**Recommendation: convert to absolute, immediately derive the delta copy, and hold the training choice** until those
two runs land. Then use whichever they support -- and use the SAME choice for every arm, per `next_task.md` section 8.
The registered prediction for the delta runs, so you can check it: ~1.0-1.5 deg open-loop (the nulls are 1.20 deg for
"stay where you are" and the demonstration floor is 0.61 deg; the absolute arm is 3.24 deg).
## 7. Commands
```bash
# baseline arm -- runs TODAY with no missing artifacts, openvla-7b downloads from HF
PY=<env>/bin/python INIT=none LATENT=none QUERY_TOKENS=4 \
DS=press_button EXTRA="--data_root_dir $PWD/data/real_rlds --merge False" \
SKIP_EVAL=1 GLOBAL=64 BS=4 STEPS=3000 SAVE=500 \
NOTE=openvla_press_button GPUS="0" RUNROOT=<volume>/vla_runs \
bash acdc_la/scripts/run_vla_post2_portable.sh
# our arm -- needs the phase-2 base, see section 8
PY=<env>/bin/python INIT=<dir containing vla_full.pt> \
DS=press_button EXTRA="--data_root_dir $PWD/data/real_rlds --merge False" \
SKIP_EVAL=1 GLOBAL=64 BS=4 STEPS=3000 SAVE=500 \
NOTE=terra_press_button GPUS="0" RUNROOT=<volume>/vla_runs \
bash acdc_la/scripts/run_vla_post2_portable.sh
# merge separately -- NEVER in-run (--merge False above). 30.7 GB peak, 211 s.
<env>/bin/python -m acdc_la.scripts.merge_ctrl_run --run <run dir> [--init <phase-2 dir>]
# offline check, in DEGREES through the deployment image path
PYTHONPATH=.:third_party/UniVLA <env>/bin/python -m acdc_la.realrobot.piper_policy \
--checkpoint <merged run dir> --replay data/real_rlds --dataset press_button
```
`SKIP_EVAL=1` is required: the LIBERO simulator eval does not apply to real-robot data.
## 8. Getting the phase-2 base
It is on HuggingFace as of 2026-11-10, in the **public** `Potestates/TERRA` repo -- **no token, no login**
(verified anonymously: a range request on `p2fft_20k/vla_full.pt` with no credentials returns HTTP 206):
```bash
huggingface-cli download Potestates/TERRA --include "p2fft_20k/*" --local-dir .
# then: INIT=./p2fft_20k ...
```
(There is also a private copy at `Potestates/TERRA-phase2-base`, kept as a backup. You do not need it.)
**Check you got both big files before training**, and against these exact sizes:
```
vla_full.pt 15,081,838,026 B the base the LoRA goes on
action_decoder.pt 105,268,830 B REQUIRED -- finetune_libero_ctrl.py:273 initialises the post-training
heads from it (strict=False). Without it the heads start RANDOM and the
run does not reproduce the workstation's arm.
```
Both arms therefore need nothing from us. **Start with the baseline anyway** (section 7, `INIT=none`): it is the
control the other arm is measured against, and it needs only `openvla/openvla-7b`.
## 9. Do not
* Do not change `window_size`, `lora_rank`, `learning_rate`, the global batch, or `FlowHeads`' `n_latents` for this
arm alone. They are shared with every LIBERO cell and with the baseline; changing one side voids the comparison.
* Do not run the in-run merge (`--merge False` is in the commands for a reason).
* Do not report `action_l1_1step` from `train_log.jsonl` as a prediction error. It is **oracle-conditioned**:
`FlowHeads.loss` computes `(at + (1-t)*va - actions).abs()` where `at` already contains the ground-truth action.
It is valid for ranking checkpoints under one fixed protocol and for comparing two arms on the SAME dataset and
normalisation, and for nothing else. The number a robot would see comes from `piper_policy.py --replay`.
* Do not quote offline L1 as if it predicted success rate. It does not.
|