YAML Metadata Warning:empty or missing yaml metadata in repo card
Check out the documentation for more information.
belt_assembly β real-robot CCIL checkpoints (2026-09-11)
Diffusion-policy checkpoints for belt_assembly, a DROID real-robot manipulation task, trained with the CCIL-in-visual-observation-spaces pipeline (baseline BC β sequential CCIL β end-to-end CCIL).
Read the caveats before using these numbers. Two of the three arms are snapshots of work in progress, not finished results. See Status below.
Data
| source | DROID capture, belt_assembly/success/2026-09-10 |
| episodes | 50 recorded, 49 used |
| transitions | 12,804 |
| cameras | agentview_image, robot0_eye_in_hand_image @ 140Γ140 uint8 |
| state | robot0_eef_pos (3) + robot0_eef_quat (4) + robot0_gripper_qpos (2) = 9 |
| actions | 7-dim absolute (pos 3, axis-angle 3, gripper 1) β rotation_6d β 10-dim |
demo_3 is excluded by the episode-quality labels. Every other episode starts
and ends at the same home pose (cohort median end-distance 0.1 mm);
demo_3 ends 10.5 cm away, ranked 50/50. The protocol is home β task β home,
and it never returns β a truncated recording. The grasp itself did occur (all
50 episodes contain exactly one gripper close).
Model selection β no simulator
This is real-robot data: there is no robosuite environment and /data carries
no env_args, so there is no rollout success rate. Every arm runs with
eval_every=0 and selects best.pt by executed-chunk action MSE on a
held-out split (select_best_by_val_loss=true).
The validation split is 5 episodes. Treat small differences with suspicion: in the e2e arm the spread between 1st and 5th place in the top-k heap is ~7%.
Contents
| path | arm | best epoch | val action MSE |
|---|---|---|---|
baseline/ |
BC baseline | 60 | 0.003426 |
seq_be/ |
sequential CCIL, clean dynamics | 10 | 0.003401 |
e2e/ |
end-to-end CCIL | 10 | 0.003215 |
e2e_fix_stepscale/ |
e2e, aug_step_scale=1.6 |
10 | 0.003260 |
e2e_fix_lossweight/ |
e2e, loss_normalization=fixed, lambda_aug=0.3 |
10 | 0.003258 |
e2e_fix_combined/ |
e2e, all of the above + aug_q_threshold=0.8, phase1=120 |
10 | 0.003258 |
Every arm carries *_best.pt (validation-selected) and final_epoch300.pt
(last epoch). They differ: best is typically epoch 10, final is epoch 300.
Each directory holds *_best.pt, val_top_k.json (the full top-k selection
record), and where available the hydra overrides.yaml.
Status β what these checkpoints actually are
baseline/ β complete and valid. 300 epochs, finished.
seq_be/ β now the clean-dynamics rerun (this replaces the earlier upload).
The first attempt was built on a dynamics model that had stalled: correct_il's
trainer had no guard against non-finite gradients, so on these 6921-dim latents
it diverged, kept training on NaN, and saved an all-NaN model (72/72 tensors).
After the guards were ported it survived but every gradient step was skipped
from ~epoch 450, so training froze. That version scored 0.003440 β worse than
the baseline it fine-tuned from β and is preserved for comparison as the wandb
run belt_assembly_dynamics_earlystop_e426.
What is published here is the rerun on dynamics capped at 400 epochs, which logged a loss in all 400 epochs, skipped 59 of ~4,000 steps (1.5%), and ended with 0 non-finite parameters. It scores 0.003401, marginally better than the baseline β but by 0.7%, on a 5-episode validation split whose 1st-to-5th spread is ~7%. Treat that as "no measurable difference", not a win.
Note the dynamics prediction error barely moved between the stalled and clean
models (per_dim_rmse 0.1215 vs 0.1219), so there was little headroom for the
seq result to change β the stall was not what was holding it back.
e2e/ β complete (300/300 epochs), but the augmentation branch never helped.
Across the full run no checkpoint ever beat epoch 10, which sits inside the
warmup phase (e2e_phase1_epochs=50) where the augmentation branch is not yet
active. So best.pt is effectively the baseline lightly fine-tuned, and
0.003215 should not be read as e2e beating the baseline. The top-5 by
validation:
| epoch | val action MSE |
|---|---|
| 10 | 0.003215 (best; pre-augmentation) |
| 150 | 0.003411 |
| 50 | 0.003412 |
| 140 | 0.003427 |
| 160 | 0.003449 |
Epochs 140-160 land on the baseline's 0.003426, while training BC loss more
than halved (0.0239 -> 0.0112) β an overfitting signature on 44 training
episodes. final_epoch300.pt is included alongside best.pt so the gap between
last-epoch and val-selected weights is inspectable.
Why augmentation never helped β the measured cause
The latent dynamics head barely models the dynamics. fwd_image_nmse is the
normalized one-step prediction error, where 1.0 means no better than predicting
"no change":
| epoch | 10 | 50 | 100 | 150 | 200 | 299 |
|---|---|---|---|---|---|---|
| e2e | 0.968 | 0.852 | 0.779 | 0.696 | 0.631 | 0.549 |
At epoch 50, when augmentation switches on, the head explains roughly 15% of
the latent displacement variance; even after 300 epochs it reaches only ~45%.
Backward-Euler synthesizes z_g = z_prev - step * fwd_head(z_prev, a_t) and then
trains the policy to emit a_t at z_g. When fwd_head is that wrong, z_g is
largely noise and the policy is taught incorrect state-action associations. CCIL
assumes the dynamics model is good enough to place a corrective label; on 44
training episodes in a 6921-dim latent space, it is not.
Three tuning arms confirm this and rule out hyperparameters as the cause. Each was run for a full 300 epochs from the same baseline checkpoint:
| arm | best | top-5 epochs | post-augmentation checkpoints in top-5 |
|---|---|---|---|
| original | 0.003215 | 10, 150, 50, 140, 160 | 150, 140, 160 |
| stepscale | 0.003260 | 10, 20, 40, 50, 30 | none |
| lossweight | 0.003258 | 10, 20, 40, 50, 30 | none |
| combined | 0.003258 | 10, 20, 40, 50, 70 | none |
The arm with the weakest augmentation β the original β is the only one whose
post-augmentation checkpoints placed at all. Correcting the displacement
(aug_step_scale=1.6, bringing effective displacement from 0.645 to ~0.98 of
true), relaxing the mask (aug_q_threshold=0.8, verified: mask_ratio 0.500 ->
0.797), reducing the augmentation weight, and delaying warmup to epoch 120 each
made post-augmentation checkpoints less competitive, not more. Amplifying a
mostly-wrong displacement amplifies the error.
Meanwhile training BC loss more than halved in every arm β the policy is overfitting 44 episodes, and augmentation does not counteract it.
Secondary issues, real but downstream. Three advertised quality gates are
inert in this code path: aug_cycle_threshold (its cycle_error_mean is
assigned to zero and never computed), aug_hardness_min (hard_ratio is
hardcoded to 1.0), and the mask's quantile filter, which keeps exactly 50% by
construction regardless of absolute quality β and since label_err is the
novelty metric, it keeps the least-displaced half, selecting against novelty.
Worth fixing, but they are not why the method fails here.
What would actually change the result: gate augmentation on measured
fwd_image_nmse rather than an epoch count (e.g. require < 0.3), and give the
dynamics head materially more data. A 100-episode session exists and is being
evaluated separately.
Reproducing
Pipeline lives on the caiyi_real_robot branch of personalrobotics/image_ccil.
# 1. preprocess + quality labels
./prepare_real_robot_data.sh RAW.hdf5 PROCESSED.hdf5 QUALITY.json
# 2. inspect trajectories; flag geometric outliers
python plot_real_wrist_trajectories.py PROCESSED.hdf5 --output wrist.png
# 3. mark bad episodes
python create_episode_quality_labels.py PROCESSED.hdf5 QUALITY.json --set-zero demo_3 --force
# 4. baseline -> seq -> e2e
REAL_TASK=belt_assembly DATASET_TASK=belt_assembly TASK_TAG=real_belt_assembly_50 \
CCIL_ENV_NAME=BeltAssembly-v0 \
bash run_real_hang_cup_baseline.sh all -1
Shared settings: DINOv2-small encoder, patch_hw=[3,3], crop 128 from 140,
pred_horizon=16, action_horizon=8, obs_horizon=1, batch 64, seed 42,
val_ratio=0.1. Latent dim = 2 Γ 3 Γ 3 Γ 384 + 9 = 6921.