PPS β€” PiperX bimanual, grind-and-tamp

Proxy Policy Steering for the PiperX bimanual robot on axiboai/piper_tamping ("grind coffee beans and then tamp").

Steering never modifies the base policy. At every denoising step the two proxies' velocity difference is added to the frozen base velocity:

v = v_base + steer_scale * (v_task - v_reference)

Contents

dir what notes
base/ frozen pi0.5, 14-D bimanual joint space PyTorch conversion of axiboai/pi05_piperx_filter_joint @ 15000
reference/ reference proxy, distilled from base/ step 12000
task/ task proxy, fine-tuned from reference/ on tamping demos step 8000

All three carry byte-identical action norm stats (the base filter_joint set). That is required: the residual is only meaningful if the three velocity fields live in one normalized space.

task/ was initialized from this reference/. They are a matched pair β€” the residual cancels only because both proxies share an initialization and so share their approximation error. Do not mix with a reference from another run.

Optimizer state is not included; these are for inference, not resuming.

Configuration

  • base: pi05_piperx_bimanual, action_horizon 50, padded action_dim 32
  • proxies: proxy_piperx_bimanual_pi05, action_dim 14, DINOv3 ViT-S/16 + gemma_12m
  • wrist-only: cam_left_wrist, cam_right_wrist; base_0_rgb is zero-padded and masked off
  • state/action layout: [L_arm_6 (rad), L_grip (m), R_arm_6 (rad), R_grip (m)]

Serving

Code: https://github.com/axiboai/piperx-pps

git clone https://github.com/axiboai/piperx-pps && cd piperx-pps/openpi
uv sync && scripts/install_transformers_replace.sh
python fetch_dinov3.py

hf download axiboai/pps_piperx_tamping --local-dir checkpoints/pps_piperx_tamping
CK=checkpoints/pps_piperx_tamping

uv run python scripts/steer_policy.py \
  --base_model_name  pi05_piperx_bimanual       --base_checkpoint_dir  $CK/base \
  --steer_model_name proxy_piperx_bimanual_pi05 --steer_checkpoint_dir $CK/task \
  --mimic_model_name proxy_piperx_bimanual_pi05 --mimic_checkpoint_dir $CK/reference \
  --steer_scale 0.4 --num_steps 10 --port 8000

--steer_scale 0.4 is the paper default; 0.4–0.6 is the useful band. 0 recovers the unsteered base, and --only_steer discards the base velocity entirely.

Inference skips the repack transform, so a client sends observations already in the transform key layout:

{
  "state": np.ndarray,            # (14,) radians + metres
  "images": {                     # CHW uint8, or float in [0,1]
    "cam_left_wrist":  ...,
    "cam_right_wrist": ...,
  },
  "prompt": "grind coffee beans and then tamp",
}

Training

Both proxies trained on a single RTX 5090 (32 GB).

  • reference: distill_pytorch.py, batch 8, peak LR 6.25e-6, 12000 steps
  • task: train_pytorch.py, batch 8, peak LR 1.5e-6, from reference/12000

Batch size and LR move together. At batch 8 the inherited 2.5e-5 (tuned for the DROID proxies at batch 32) diverges: distillation loss bottoms near 0.08 by step 3000, then climbs past 0.9. The task proxy needed a further reduction to 1.5e-6 β€” at 6.25e-6 it bottomed at 0.170 by step 1500 and rose to 0.31 by step 10000.

Task-proxy loss, mean over 1000-step windows:

steps loss
0–999 0.2050
1000–1999 0.1580
2000–2999 0.1528
3000–3999 0.1523
4000–4999 0.1516
5000–5999 0.1547
6000–6999 0.1500
7000–7999 0.1486
8000–8999 0.1498

Flat from ~3000 on; task/8000 closes the best window. Training was stopped at step 9203 rather than the scheduled 20001, since the plateau was unambiguous.

Not yet validated on hardware. The loss curves say the proxies converged; they do not say the steered policy completes the task. The base was fine-tuned on a different task (portafilter insertion) and has never seen tamping, which is the intended PPS regime, but the success rate is unmeasured.

Downloads last month

-

Downloads are not tracked for this model. How to track
Video Preview
loading