Handoff brief: training the press_button arm on a Jetstream2 g3.xl
Browse files- HANDOFF_press_button.md +229 -0
HANDOFF_press_button.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Handoff: the `press_button` arm on a Jetstream2 g3.xl
|
| 2 |
+
|
| 3 |
+
Written 2026-11-10 by the workstation session for whoever picks this up on Jetstream2. Everything below that is a
|
| 4 |
+
measurement was measured; everything that is a guess says so. Read the "unverified" notes before trusting a number.
|
| 5 |
+
|
| 6 |
+
## 0. Read these first, in this order
|
| 7 |
+
|
| 8 |
+
1. `AGENTS.md` (repo root) -- **the experiment ledger is `LEDGER.md` at the repo root, and it must be updated before
|
| 9 |
+
you report a result to the user.** Configuration, split, budget, the number, its control, conclusion status,
|
| 10 |
+
artifact paths, next decision. Do not leave a conclusion only in chat or in a subdirectory README.
|
| 11 |
+
2. `LEDGER.md`, the entry **2026-11-10 "FIRST PHYSICAL-ROBOT feedback on the Piper policy"**. It has everything
|
| 12 |
+
already measured about this robot and policy family. Do not re-measure it.
|
| 13 |
+
3. `next_task.md` section 8: **never discretise our latent for the VLA; never change a shared setting for one arm
|
| 14 |
+
only.** The second one is the live constraint here -- see section 6.
|
| 15 |
+
|
| 16 |
+
## 1. What this run is for
|
| 17 |
+
|
| 18 |
+
`anhdao69/press_button_task` is the **first multi-task real-robot dataset in this project**. The pick-can set has
|
| 19 |
+
exactly one task, which means the instruction carried **zero information**: the policy could ignore the language
|
| 20 |
+
entirely and nothing would change. The press-button set contains at least `"press the red button"` and
|
| 21 |
+
`"press the blue button"` -- same scene, same motion class, the only disambiguator is the word. **That is the first
|
| 22 |
+
clean test of whether this policy family reads its instruction at all**, and it is worth more than a second task
|
| 23 |
+
would be on its own. Register that as the question before you train, not after.
|
| 24 |
+
|
| 25 |
+
## 2. The machine: what fits on a g3.xl
|
| 26 |
+
|
| 27 |
+
| | |
|
| 28 |
+
|---|---|
|
| 29 |
+
| g3.xl | 32 vCPU, **120 GB system RAM**, **40 GB GPU RAM** (a full A100), 64 SU/h |
|
| 30 |
+
| smaller flavors | g3.large 20 GB GPU / 60 GB RAM; g3.medium 10 GB / 30 GB. **Neither is enough.** |
|
| 31 |
+
| multi-GPU | g3.2xl / g3.4xl exist but are **not available by default** -- a justified request to help@jetstream-cloud.org |
|
| 32 |
+
|
| 33 |
+
**Measured on the workstation (RTX PRO 6000, 2026-11-10): training takes 39.9-40.8 GB of VRAM at micro-batch 8.**
|
| 34 |
+
That does not fit in 40 GB with any headroom. So:
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
BS=4 GA=16 estimated ~28 GB <- use this
|
| 38 |
+
BS=8 GA=8 40 GB measured <- will OOM on an A100
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
**The global batch must stay 64** (`BS x GA x GPUs = 64`); the launcher asserts it. Every Piper and 10 % LIBERO cell
|
| 42 |
+
in this project is 64, and changing it makes the run incomparable with all of them. The ~28 GB figure is an
|
| 43 |
+
extrapolation from one measured point (fixed ~16 GB + activations linear in batch), **not measured** -- check
|
| 44 |
+
`nvidia-smi` at step 50 and drop to BS=2 GA=32 if it is close to the limit.
|
| 45 |
+
|
| 46 |
+
Other facts worth having before you start:
|
| 47 |
+
|
| 48 |
+
* **Disk: plan 80-100 GB and attach a volume.** `openvla-7b` is 15 GB, the env is ~11 GB, each merged checkpoint is
|
| 49 |
+
16 GB, the phase-2 base (if you get it) is another 15 GB.
|
| 50 |
+
* **120 GB RAM is the one place Jetstream2 beats the workstation.** The standalone merge peaks at **30.7 GB** and was
|
| 51 |
+
SIGKILLed three times on the 62 GB workstation (LEDGER 2026-11-08). On g3.xl it is a non-issue.
|
| 52 |
+
* **Measure the first 100 steps before quoting an ETA to anyone.** On 2026-11-10 the workstation estimate of 13 h
|
| 53 |
+
became 18 h because another user's job was on the same card. `train_log.jsonl` has a `seconds` field; use it.
|
| 54 |
+
|
| 55 |
+
## 3. Environment
|
| 56 |
+
|
| 57 |
+
Follow **`REPRODUCE.md`, the `univla_vla` recipe** (torch 2.2.0 + cu121, `pip install -e third_party/UniVLA`, the
|
| 58 |
+
pinned `tensorflow_metadata` / `protobuf` / `wandb`). That recipe targets sm80, which **is** the A100 -- do not copy
|
| 59 |
+
this workstation's env, which is torch 2.7.1+cu128 built for Blackwell.
|
| 60 |
+
|
| 61 |
+
Two things that save time:
|
| 62 |
+
|
| 63 |
+
* **Training never asks for flash-attention.** Grep confirms `attn_implementation` appears only in the two inference
|
| 64 |
+
scripts (`run_libero_eval_cont.py`, `realrobot/piper_policy.py`) and both use `"sdpa"`. So you do not need to build
|
| 65 |
+
flash-attn for sm80.
|
| 66 |
+
* **Conversion needs `pyarrow` and `av`** on top of the env (`cv2` too, which the env has). The workstation does not
|
| 67 |
+
have them, which is why this dataset was not converted there.
|
| 68 |
+
|
| 69 |
+
## 4. The dataset, and three things that will bite you
|
| 70 |
+
|
| 71 |
+
Verified from `meta/info.json` and `meta/stats.json` (read without downloading the videos):
|
| 72 |
+
|
| 73 |
+
```
|
| 74 |
+
robot_type piper | LeRobot codebase_version v3.0 | fps 30
|
| 75 |
+
total_episodes 100 | total_frames 5533 | total_tasks 4
|
| 76 |
+
action float32 [7] joint_1.pos .. joint_6.pos + gripper (ABSOLUTE joint positions, degrees)
|
| 77 |
+
observation.state float32 [14]
|
| 78 |
+
observation.images.front / .wrist video 480x640x3 mp4
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
Same robot, same action space and same layout as `pick_can_place_plate_150_trimmed`, so
|
| 82 |
+
`acdc_la/realrobot/convert_piper_rlds.py` reads it **without a code change** to its parsing. But:
|
| 83 |
+
|
| 84 |
+
### (a) `GRIP_SCALE = 8.2` is WRONG for this dataset
|
| 85 |
+
|
| 86 |
+
`convert_piper_rlds.py:20` divides the raw gripper by 8.2 because the pick-can set's raw gripper ranges 0 .. 8.15.
|
| 87 |
+
Here it does not:
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
action dim 6: min 0.04 max 0.06 mean 0.05 std 0.01
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
Pressing a button never opens the gripper. `clip(act[:,6] / 8.2, 0, 1)` maps all of it to ~0.005 -- a **constant**.
|
| 94 |
+
Numerically harmless, but that dimension carries no signal, and if the two datasets are ever trained jointly the
|
| 95 |
+
gripper calibrations are incompatible. **Make a per-dataset decision and write it in the converter's docstring.**
|
| 96 |
+
|
| 97 |
+
### (b) The episodes are very short, and a 12-step chunk may not fit in them
|
| 98 |
+
|
| 99 |
+
```
|
| 100 |
+
5533 frames / 30 Hz, subsampled by 3 -> ~1,844 steps at 10 Hz (pick-can: 6,462, i.e. 3.5x more)
|
| 101 |
+
1,844 / 100 episodes -> ~18 steps = 1.8 s per episode
|
| 102 |
+
window_size 12 = 1.2 s -> two thirds of an entire episode
|
| 103 |
+
RLDSDataset is built with window_size + 2 = 14 frames per sample -> ~5 valid windows per episode
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
**Verify what prismatic's chunking actually does at an episode boundary before you believe any loss curve.** I did
|
| 107 |
+
not check this and it is the most likely silent failure: if it pads, you are training on padding for a large fraction
|
| 108 |
+
of every sample. `prismatic/vla/datasets/rlds/traj_transforms.py::chunk_act_obs_libero` is the function.
|
| 109 |
+
|
| 110 |
+
### (c) Overfitting is a real risk here -- unlike on pick-can
|
| 111 |
+
|
| 112 |
+
```
|
| 113 |
+
1,844 steps of data / global batch 64 -> 29 steps per epoch
|
| 114 |
+
10,000 steps -> 347 epochs (pick-can at 10k was 104)
|
| 115 |
+
95/5 split of 100 episodes -> 95 train, only 5 held out
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
The collaborator's "is it under- or over-trained?" question was a **negative** for pick-can (held-out L1 flat from
|
| 119 |
+
step 6,000 to 10,000 while train L1 still fell). **On this dataset it will genuinely bite.** So:
|
| 120 |
+
|
| 121 |
+
* Pick a smaller budget. ~3,000 steps is ~104 epochs, matching pick-can's epoch count, which is the defensible
|
| 122 |
+
choice -- not 10,000.
|
| 123 |
+
* **Actually watch the held-out curve this time**, and snapshot checkpoints: `finetune_libero_ctrl.py:496` writes
|
| 124 |
+
every save to the SAME adapter dir, so each save overwrites the last and the run keeps **no history**. Copy
|
| 125 |
+
`<run_root>/adapters/<exp_id>` after each save or you cannot pick a step afterwards.
|
| 126 |
+
* Note `total_tasks: 4` but only two task strings were legible to me (`press the red button`, `press the blue
|
| 127 |
+
button`). Read `meta/tasks.parquet` properly (needs pyarrow) and report what the 4 are.
|
| 128 |
+
|
| 129 |
+
## 5. Registering the dataset
|
| 130 |
+
|
| 131 |
+
`convert_piper_rlds.py` builds a TFDS builder whose **dataset name comes from the class name** (`PiperPickCan` ->
|
| 132 |
+
`piper_pick_can`), and `piper_pick_can` is registered in prismatic by `acdc_la/patches/univla_piper.patch` (image
|
| 133 |
+
keys + the `EEF_POS` contract). For a new dataset, copy the converter to `convert_press_button_rlds.py`, rename the
|
| 134 |
+
builder class to `PressButton`, and add a `press_button` entry to the patch's two tables (`OXE_DATASET_CONFIGS` in
|
| 135 |
+
`oxe/configs.py` and `OXE_STANDARDIZATION_TRANSFORMS` in `oxe/transforms.py`) -- the Piper entries can be copied
|
| 136 |
+
verbatim, both datasets have the same image keys and action contract.
|
| 137 |
+
|
| 138 |
+
**A gotcha I already hit:** TFDS refuses to build a builder class whose module is `__main__`
|
| 139 |
+
("Cannot access resources for '__main__'"). Run it as an import, not as a script:
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
PYTHONPATH=.:third_party/UniVLA python -c "
|
| 143 |
+
import sys; sys.argv = ['x', '--src', '<lerobot dir>', '--out', 'data/real_rlds']
|
| 144 |
+
from acdc_la.realrobot.convert_press_button_rlds import main; main()"
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
The shortcut of reusing the `piper_pick_can` registration under a different `--data_root_dir` works (that is what the
|
| 148 |
+
delta copy does) but gives both datasets the same key in `dataset_statistics.json`, which is a real footgun. Register
|
| 149 |
+
a proper name.
|
| 150 |
+
|
| 151 |
+
## 6. Absolute or delta actions? Do not decide this alone
|
| 152 |
+
|
| 153 |
+
Two 10,000-step runs are in flight on the workstation **right now** testing exactly this on pick-can, both on delta
|
| 154 |
+
actions at global batch 64 (expected to finish ~2026-11-10 22:00 UTC):
|
| 155 |
+
|
| 156 |
+
* `tc_cont+piper_pick_can+b64x1+...--terra_piper_delta` -- our phase-2 base
|
| 157 |
+
* `none+piper_pick_can+b64x1+...--openvla_piper_delta` -- official openvla-7b, no latent mechanism (the baseline)
|
| 158 |
+
|
| 159 |
+
Why: the absolute parameterisation spends a 164 deg output range on a ~2 deg/step signal (1.2 %), against a measured
|
| 160 |
+
error of 2.4-11.5 % of each joint's range -- the signal sits below the error (LEDGER 2026-11-10). The delta is
|
| 161 |
+
`action[:6] - joint_state[:6]`, target minus the step's own **measured** configuration, which is self-correcting at
|
| 162 |
+
deployment; `acdc_la/realrobot/make_delta_rlds.py` derives a delta RLDS from an absolute one in ~3 minutes with the
|
| 163 |
+
frames bit-identical.
|
| 164 |
+
|
| 165 |
+
**Recommendation: convert to absolute, immediately derive the delta copy, and hold the training choice** until those
|
| 166 |
+
two runs land. Then use whichever they support -- and use the SAME choice for every arm, per `next_task.md` section 8.
|
| 167 |
+
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
|
| 168 |
+
"stay where you are" and the demonstration floor is 0.61 deg; the absolute arm is 3.24 deg).
|
| 169 |
+
|
| 170 |
+
## 7. Commands
|
| 171 |
+
|
| 172 |
+
```bash
|
| 173 |
+
# baseline arm -- runs TODAY with no missing artifacts, openvla-7b downloads from HF
|
| 174 |
+
PY=<env>/bin/python INIT=none LATENT=none QUERY_TOKENS=4 \
|
| 175 |
+
DS=press_button EXTRA="--data_root_dir $PWD/data/real_rlds --merge False" \
|
| 176 |
+
SKIP_EVAL=1 GLOBAL=64 BS=4 STEPS=3000 SAVE=500 \
|
| 177 |
+
NOTE=openvla_press_button GPUS="0" RUNROOT=<volume>/vla_runs \
|
| 178 |
+
bash acdc_la/scripts/run_vla_post2_portable.sh
|
| 179 |
+
|
| 180 |
+
# our arm -- needs the phase-2 base, see section 8
|
| 181 |
+
PY=<env>/bin/python INIT=<dir containing vla_full.pt> \
|
| 182 |
+
DS=press_button EXTRA="--data_root_dir $PWD/data/real_rlds --merge False" \
|
| 183 |
+
SKIP_EVAL=1 GLOBAL=64 BS=4 STEPS=3000 SAVE=500 \
|
| 184 |
+
NOTE=terra_press_button GPUS="0" RUNROOT=<volume>/vla_runs \
|
| 185 |
+
bash acdc_la/scripts/run_vla_post2_portable.sh
|
| 186 |
+
|
| 187 |
+
# merge separately -- NEVER in-run (--merge False above). 30.7 GB peak, 211 s.
|
| 188 |
+
<env>/bin/python -m acdc_la.scripts.merge_ctrl_run --run <run dir> [--init <phase-2 dir>]
|
| 189 |
+
|
| 190 |
+
# offline check, in DEGREES through the deployment image path
|
| 191 |
+
PYTHONPATH=.:third_party/UniVLA <env>/bin/python -m acdc_la.realrobot.piper_policy \
|
| 192 |
+
--checkpoint <merged run dir> --replay data/real_rlds --dataset press_button
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
`SKIP_EVAL=1` is required: the LIBERO simulator eval does not apply to real-robot data.
|
| 196 |
+
|
| 197 |
+
## 8. Getting the phase-2 base
|
| 198 |
+
|
| 199 |
+
It is on HuggingFace as of 2026-11-10, in a **private** repo (the project's other HF repo, `Potestates/TERRA`, is
|
| 200 |
+
public, so the base was not put there):
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
huggingface-cli login # the user has to give you a token with read access to their account
|
| 204 |
+
huggingface-cli download Potestates/TERRA-phase2-base --local-dir p2fft_20k
|
| 205 |
+
# then: INIT=./p2fft_20k ...
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
**Check you got both big files before training**, and against these exact sizes:
|
| 209 |
+
|
| 210 |
+
```
|
| 211 |
+
vla_full.pt 15,081,838,026 B the base the LoRA goes on
|
| 212 |
+
action_decoder.pt 105,268,830 B REQUIRED -- finetune_libero_ctrl.py:273 initialises the post-training
|
| 213 |
+
heads from it (strict=False). Without it the heads start RANDOM and the
|
| 214 |
+
run does not reproduce the workstation's arm.
|
| 215 |
+
```
|
| 216 |
+
|
| 217 |
+
If the token is not available, the **baseline arm still runs today** (section 7, `INIT=none`): `openvla/openvla-7b`
|
| 218 |
+
is public and downloads without credentials. Start with that one either way -- it needs nothing from us.
|
| 219 |
+
|
| 220 |
+
## 9. Do not
|
| 221 |
+
|
| 222 |
+
* Do not change `window_size`, `lora_rank`, `learning_rate`, the global batch, or `FlowHeads`' `n_latents` for this
|
| 223 |
+
arm alone. They are shared with every LIBERO cell and with the baseline; changing one side voids the comparison.
|
| 224 |
+
* Do not run the in-run merge (`--merge False` is in the commands for a reason).
|
| 225 |
+
* Do not report `action_l1_1step` from `train_log.jsonl` as a prediction error. It is **oracle-conditioned**:
|
| 226 |
+
`FlowHeads.loss` computes `(at + (1-t)*va - actions).abs()` where `at` already contains the ground-truth action.
|
| 227 |
+
It is valid for ranking checkpoints under one fixed protocol and for comparing two arms on the SAME dataset and
|
| 228 |
+
normalisation, and for nothing else. The number a robot would see comes from `piper_policy.py --replay`.
|
| 229 |
+
* Do not quote offline L1 as if it predicted success rate. It does not.
|