ODE Policy (Euler solver) β R1 Lite "open drawer, multimodal" (two arms)
Checkpoints of the ODE policy trained on real-robot demonstrations of the Galaxea R1 Lite opening a drawer with either arm: in each demonstration one arm grasps the handle and pulls (left in 25, right in 25 of the 50 episodes). The policy learns a velocity field Θ§ = f(c, s, a) over the 12 joint targets of both arms and produces each 8-step action chunk by integrating that field with the explicit Euler solver (one field evaluation per control step, training and inference alike) from the current target.
Files
| Path | Content |
|---|---|
checkpoints/{50,100,150,200,250,300}.ckpt |
Full training checkpoints: model, ema_model, optimizer state dicts + the pickled Hydra config. Serve the EMA weights (the loader does this by default). |
config/config.yaml, config/overrides.yaml |
Resolved training config and the command-line overrides of this run |
logs/logs.json.txt |
Per-step / per-epoch training log (loss, lr, epoch time, val_loss every 5 epochs) |
SHA256SUMS |
Checksums of every uploaded file |
Checkpoints are torch.save(..., pickle_module=dill) payloads written by the ODE_Policy training runner.
Loading them needs that codebase at a version with two-arm support (branch r1lite_exp, commit e9cc9c0
or later: ODEImagePolicy with n_grippers / state_pose_index) and
torch.load(..., pickle_module=dill, weights_only=False) β only load checkpoints you trust.
Data
at237299966/r1lite_open_drawer_multimodal:
LeRobot v2.1, 50 episodes / 15,896 frames at 15 Hz (268β393 frames each), task "open drawer multimodal".
| Input / output | Definition |
|---|---|
| Cameras | head_rgb (720Γ1280), right_wrist_rgb and left_wrist_rgb (360Γ640), padded to square then resized to 256Γ256 (same geometry as the robot client) |
agent_pos |
14-D, the robot client's order: [L1..L6 (rad), L_grip (mm), R1..R6 (rad), R_grip (mm)] |
ODE state a |
the 12 absolute joint targets [L1..L6, R1..R6] (rad). Row convention: a[t] = commanded target at tβ1 (episode start: measured joints) |
| Gripper head | binary per gripper (left, right), +1 close / β1 open. The grippers close onto the handle and stop part-way (held at 25β55 mm, median 43 mm) and snap open, so the labels use plateau detection (--gripper-ramp-mask moving, threshold 75 mm): the label flips where the operator starts closing and where the release starts |
| Window | 8 observation frames, 8-step action chunk |
Training
| Model | ResNet18 (GroupNorm, random crop) per camera (3 cameras), 8 frames β condition c (512); vector field = 4-layer FiLM-MLP, width 512; proprioception enters the field only (cond_use_s=false); separate gripper head (2 logits per step) |
| Integration | Euler, 1 substep, 8 control steps per chunk, vel_scale=0.05; observation fixed over the chunk (obs_update=fixed) |
| Start perturbation | 50% of samples, per-joint std 0.005 rad except L2 0.0184, L3 0.0111, R2 0.023, R3 0.0131 rad, decay 0.7 per step |
| Optimisation | AdamW, lr 1.5e-4, cosine schedule over 300 epochs, warm-up 500 steps, EMA |
| Batch | 2 Γ A100-40GB (DDP), 40 per GPU = 80 global (the three-camera setting of the two-arm runs) |
| Wall clock | 9 h 19 min (112 s/epoch), 300 epochs, 194 steps/epoch, 58,200 steps in total |
| epoch | 0 | 10 | 50 | 100 | 150 | 200 | 250 | 300 |
|---|---|---|---|---|---|---|---|---|
| train loss | 1.11 | 0.252 | 0.0614 | 0.0334 | 0.0201 | 0.0132 | 0.00926 | 0.00816 |
| val loss | 0.696 | 0.672 | 0.662 | 0.664 | 0.75 | 0.755 | 0.794 | 0.798 (ep 295) |
Lowest validation loss: 0.557 at epoch 5 (validation every 5 epochs). Columns 50, 100, ... are the saved checkpoints (checkpoints/<epoch>.ckpt).
The validation loss is a weak signal: one held-out episode, raw (non-EMA) weights, with the training start perturbation still active. Compare early and late checkpoints on the robot before picking one.
Deployment
The robot side is the unmodified openpi-r1lite closed-loop client (main_r1lite_closedloop.py, 15 Hz,
executes row 0 of every (8, 14) reply; it must send all three cameras). The server reads the two-arm layout
and the Euler solver from the checkpoint, re-integrates a chunk every 8 ticks from the last target it sent and
walks the client through it; both arms are commanded:
python -m roboverse_learn.il.deploy.serve_policy \
--checkpoint checkpoints/100.ckpt --host 0.0.0.0 --port 8000 --device cuda:0 \
--gripper_close 43 --gripper_open 100 --gripper_ramp_ticks 31
python -m roboverse_learn.il.deploy.test_client --host 127.0.0.1 --port 8000 -n 40 --bimanual --gripper_range 43 100 # pre-flight
--gripper_close 43 is the median width the demonstrations held on the handle (commanding 0 would squeeze
harder than the operator did); --gripper_ramp_ticks 31 is the demonstrations' closing ramp (the server uses
the same rate for opening, which in the demos was a 1β2 frame step). Set the robot to the demonstrations' start
pose before a run (torso [-0.6826, 1.4936, 0.8012] rad, both arms at ~0, grippers 100; the torso is not part
of the policy's state). Code: ODE_Policy repository, neural_ode/real/ (data + training) and
roboverse_learn/il/deploy/ (server).