Use Linux, Python 3.11, a CUDA 12-compatible NVIDIA driver and the checked-in uv.lock:
GIT_LFS_SKIP_SMUDGE=1 uv sync --frozen
export HF_LEROBOT_HOME="$PWD/data/lerobot"
# Optional, to put public downloads on a large local disk:
export OPENPI_DATA_HOME="$HOME/.cache/openpi"Do not copy another machine's .venv or install a different LeRobot revision. The main recipe is JAX. The retained upstream PyTorch implementation is not an interchangeable training backend for the commands in this guide; its installation/weight-conversion requirements differ.
The source snapshot is independent of any original Git remote/submodules. No internal proxy, service token or cluster job launcher is required.
The pinned LeRobot version expects v2.1 metadata, not the newer v3 layout. A local dataset resolves as $HF_LEROBOT_HOME/<repo-id>; use local/piperx for the examples. You may use a Hugging Face dataset ID if you have access to a compatible dataset.
| Dataset column | Type/shape | Meaning |
|---|---|---|
image |
RGB image, HWC | Head/scene camera |
wrist_left_image |
RGB image, HWC | Left wrist camera |
wrist_right_image |
RGB image, HWC | Right wrist camera |
state |
float32 (14,) |
Left joints 0–5, right joints 6–11, left/right grippers 12–13 |
action |
float32 (14,) per frame |
Absolute next-state target; loader constructs the action horizon |
| task metadata | string | Natural-language instruction; loaded through task_index |
Joint units are preserved from joint_positions; the converter does not infer radians/degrees or apply calibration. Grippers are read from range_mm. Training and deployment must use the same units, coordinate conventions and joint order.
Prepare your own manifest, for example:
[
{"episode": "task_01/episode_000", "prompt": "Place the object on the plate."},
{"episode": "task_01/episode_001", "prompt": "Place the object on the plate."}
]Each relative path is resolved inside --raw-root:
raw_data/task_01/episode_000/
├── mixed_data.json
├── head_left/00000.jpg, 00001.jpg, ...
├── wrist_left/00000.jpg, 00001.jpg, ...
└── wrist_right/00000.jpg, 00001.jpg, ...
mixed_data.json contains a motion list. Each element is:
{
"frame_id": 0,
"arm_left": {"joint_positions": [0, 0, 0, 0, 0, 0]},
"arm_right": {"joint_positions": [0, 0, 0, 0, 0, 0]},
"gripper_left": {"range_mm": 20},
"gripper_right": {"range_mm": 20}
}Provide at least two consecutive, time-ordered frames per episode and the true recording FPS. Zero values above are a schema illustration, not robot calibration or a command.
uv run examples/piperx_real/convert_data.py \
--manifest /path/to/episodes.json --raw-root /path/to/raw_data \
--repo-id local/piperx --fps 30The converter resizes RGB images to 256×256, stores observation t with absolute state t+1 as its action, and drops the last observation (it has no next-state target). Model transforms subsequently resize to 224×224. It rejects duplicate/path-traversing episodes, missing data and non-finite state values. It never overwrites an existing dataset or uploads it. After an interrupted conversion, choose a fresh output directory or manually inspect/recover the incomplete dataset; implicit deletion/resume is deliberately absent.
If using --output-dir, ensure the result is still discoverable at $HF_LEROBOT_HOME/<repo-id> during normalization/training. Prefer the default output location.
JAX_PLATFORMS=cpu uv run scripts/compute_norm_stats.py \
--config-name pi05_piperx --repo-id local/piperx \
--batch-size 32 --num-workers 4This finite CPU pass includes the final partial batch and writes:
assets/pi05_piperx/piperx/norm_stats.json: state/action mean, std and approximate quantiles.assets/pi05_piperx/piperx/norm_stats_info.json: dataset/split/seed/action-convention provenance.
For this π₀.₅ recipe, training uses quantile normalization. Joint deltas are computed before action normalization; grippers stay absolute. Never compute statistics with the absolute config and then train with the delta config.
--max-frames 100 is only for testing the data path, not model training or model comparison. Full statistics must be recomputed without that flag. Training checks the generated provenance sidecar and rejects limited-frame statistics or a mismatched dataset/action convention/split. Legacy assets without a sidecar require manual verification. Replacing an existing stats file requires --overwrite explicitly. Do not use raw training loss to compare models trained with different normalization conventions.
Default validation is disabled to preserve the full-data recipe. To enable it, use identical settings in both commands:
JAX_PLATFORMS=cpu uv run scripts/compute_norm_stats.py \
--config-name pi05_piperx --repo-id local/piperx \
--val-episodes-frac 0.05 --seed 42 --overwrite
bash scripts/train_8gpu.sh --exp-name piperx_with_val \
--data.repo-id local/piperx --val-episodes-frac 0.05 --seed 42 \
--val-interval 1000 --val-batches 4The split is a deterministic seeded split of whole episodes. Normalization uses only the training subset. Validation uses EMA parameters, the same validation batches and fixed sampling noise; its MSE is measured on the 14 real normalized action dimensions, not the 32 padded model dimensions. By default only a bounded set of full batches is evaluated, not every held-out frame. The validation subset must contain at least one global batch.
This is an episode holdout, not a task-/object-/scene-disjoint benchmark. val/action_mse is a proxy for prediction quality, not a measured robot success rate.
bash scripts/train_8gpu.sh --exp-name piperx_run --data.repo-id local/piperxThe launcher:
- Uses the caller's
CUDA_VISIBLE_DEVICES, or defaults to0,1,2,3,4,5,6,7. - Requires exactly eight visible GPUs and fails rather than falling back to CPU.
- Runs one JAX process. The model/data mesh provides parallelism; do not launch eight Python copies.
- Uses the locked environment; does not set proxies, cluster interfaces or rendezvous ports.
| Setting | Default |
|---|---|
| Model | π₀.₅, action dimension 32 (14 real + padding) |
| Action horizon | 10 |
| Discrete state input | False, preserving this PiperX variant |
| Action convention | Delta joints / absolute grippers |
| Global batch | 256; 32 examples per device |
| FSDP devices | 8 |
| Steps | 15000 |
| Learning rate | Warmup 1000 to 5e-5; remains 5e-5 |
| Optimizer | AdamW, gradient clip 1.0 |
| EMA | 0.999 |
| Save / retain | Every 1000; keep latest plus multiples of 3000 |
| W&B | Disabled; local metrics always enabled |
Approximate epochs = steps × global_batch / usable_training_frames; adapt the duration to your dataset. This starting recipe is not claimed to be optimal for every dataset.
Examples of explicit overrides:
# Smaller global batch if memory is insufficient; adjust duration if matching epochs.
bash scripts/train_8gpu.sh --exp-name smaller_batch \
--data.repo-id local/piperx --batch-size 64
# Existing public base weights and a larger checkpoint disk.
bash scripts/train_8gpu.sh --exp-name local_weights \
--data.repo-id local/piperx \
--weight-loader.params-path /path/to/pi05_base/params \
--checkpoint-base-dir /path/to/checkpoints
# Resume the latest retained checkpoint of the same run.
bash scripts/train_8gpu.sh --exp-name piperx_run --data.repo-id local/piperx --resumeA direct uv run scripts/train.py pi05_piperx ... call may use fewer GPUs with compatible --fsdp-devices and --batch-size. That is not the validated 8-device launcher path, and full-model memory requirements still apply. FSDP reduces parameter/optimizer memory at communication cost; it does not make arbitrary 8×GPU hardware sufficient.
--overwrite explicitly deletes the chosen experiment directory; it is never added by the launcher. Use a new experiment name unless you intentionally want that deletion.
metrics.jsonl contains completed optimizer steps and metrics, even with no tracking account. run_info.json records the dataset, split indices, seed and key model/data settings. Optional W&B can be enabled with --wandb-enabled after you configure your own account.
Resume restores model, optimizer and EMA state; it does not restore the precise data-loader iterator position. It is not guaranteed to be bitwise identical to an uninterrupted run. Keep data, transforms, normalization, seed and model architecture unchanged when resuming.
Checkpoint params/ contains EMA inference weights when EMA is enabled. Keep train_state/ for further training. Use scripts/export_checkpoint.py only for an inference-only distribution, not a resumable run.
- No GPUs / count mismatch: run
uv run scripts/check_environment.py --expected-gpus 8; check driver installation and GPU visibility. The host must expose GPUs to the container if applicable. - Dataset not found: set the same
HF_LEROBOT_HOMEin every shell, and verifylocal/piperx/meta/info.jsonreports LeRobot v2.1. - Missing stats: recompute for the exact config, dataset and action convention. The constant asset ID
piperxdoes not mean statistics are interchangeable across datasets. - Out of memory: reduce global batch to a multiple of eight, keep FSDP=8, and use appropriate GPUs. Changing EMA or action horizon changes the recipe and must be documented.
- Small validation dataset: reduce batch size or increase held-out data; do not duplicate validation episodes silently to fabricate a benchmark.
- Offline environment: cache the locked packages, dataset, base weights and
gs://big_vision/paligemma_tokenizer.modelin advance.scripts/prefetch_assets.pyuses public anonymous storage and prints the cache locations; no private mirror is configured.