YAM Pick-Duster 200 β€” B-spline Diffusion Policy (joint space)

A B-spline Policy UNet diffusion policy trained on Dimios45/yam-pick-duster-200: 200 teleop demos of an I2RT YAM arm picking a blue duster and placing it in a red box.

The policy predicts B-spline knots and control points instead of a fixed grid of actions, giving a continuous trajectory that can be resampled at any rate and temporally rescaled at deploy time.

Commands joints directly β€” no inverse kinematics at inference. This is the model to deploy. The end-effector counterpart trained on the same 200 takes is Dimios45/yam-pick-duster-200-bspline-ee, but its dataset card advises against deploying that Cartesian path unresolved.

Supersedes the 50-episode yam-pick-duster-bspline-joint.

Action and observation space

action / obs joint_pos  (7,) = [joint1..6 (radians), gripper]
gripper: 0 = open, 1 = closed
key shape notes
top_image (3, 84, 84) RGB, RAW 640x480 resized β€” not cropped
wrist_image (3, 84, 84) RGB, right wrist
joint_pos (7,) measured joints + gripper

Two observation steps; random crop to 76x76 in training, center crop at eval.

Actions stay 7-D: with action.shape [7], _convert_actions takes the raw_dim == target_action_dim branch and passes them through untouched β€” no rotation_6d expansion. infer_action_meta resolves this to single_yam_joint.

Network output is (16, 8): column 0 is the knot vector in units of 25 Hz frames relative to the current observation, columns 1-7 are control points. 16 = chunk_size 10 + 2 x degree 3.

Files

file size use
deploy_ema.ckpt 426 MB Inference. EMA weights only.
epoch0250_full.ckpt 1.5 GB model + ema_model + optimizer, for resuming/fine-tuning.

Both embed the Hydra config (pickled with dill), so bspline_policy and diffusion_policy must be importable on load.

Training

data 200 episodes, 76,609 frames @ 25 Hz -> 76,409 B-spline chunks
hardware 1x RTX 4090, ~5.4 h, 77 s/epoch, 1,194 batches/epoch
epochs / batch 251 / 64
optimizer AdamW, lr 1e-4, cosine, 500 warmup, EMA
scheduler DDIM, 100 train timesteps, 16 inference steps, epsilon prediction
B-spline degree 3, chunk_size 10, max_error 0.002 rad, absolute knots

Loss: 0.167 (ep0) -> 0.007 (50) -> 0.005 (100) -> 0.003 (150) -> 0.002 (200-250), final in-epoch 0.00124.

The 251-epoch budget was chosen on gradient-step count (~300k steps, 1.13x the 50-episode run that converged cleanly), not copied from it β€” 601 epochs on this data would have been ~9.5 h for no benefit. checkpoint_every: 50 with 251 (not 250) epochs so the fully-annealed final epoch is actually written; saves land only on multiples of 50.

At matched epochs this beats the 50-episode model: 0.005 vs 0.007 at epoch 100.

Inference latency

Measured on an idle GPU, batch 1, two 84x84 cameras:

DDIM steps RTX 4090 CPU (i9-13900K, 8 threads)
4 13.2 ms 55.8 ms
8 23.7 ms 95.1 ms
16 44.9 ms 164.3 ms

A chunk spans ~0.4 s of wall time at 25 Hz, and inference must finish inside it. On GPU all three settings clear comfortably. On a NUC-class CPU (expect 2-3x the CPU column), 16 steps is tight β€” use --num-inference-steps 8 on CPU. Set --predict-before-end to roughly 2-3x your chosen latency.

Rollout

Requires the joint-space deployment patches (single_yam_joint decoder, yam_server joint mode bypassing IK, the top_image camera). See Dimios45/yam-duster-bspline-dp for the full list β€” they are not in upstream B-spline-policy/bspline-policy.

cd ~/bspline-policy
export PYTHONPATH=$PWD/bspline_policy:$PWD/diffusion_policy:$PWD/real_env/yam_teleop

hf download Dimios45/yam-pick-duster-200-bspline-joint deploy_ema.ckpt --local-dir ./ckpt

# terminal 1 - arm server
sudo ip link set can_follower_r up type can bitrate 1000000
python real_env/yam_teleop/yam_server.py --channel can_follower_r

# terminal 2 - rollout
python real_env/yam_teleop/rollout_local_policy.py \
  --env yam --policy bspline \
  --ckpt-path ./ckpt/deploy_ema.ckpt \
  --diffusion-policy-dir $PWD/diffusion_policy \
  --control-freq 100 \
  --data-freq 25 \
  --origin-time-scale 25 \
  --num-inference-steps 16 \
  --predict-before-end 0.15 \
  --speed-up-times 1.0 \
  --save --output-dir data/rollouts_joint_200

Flags that are not optional

flag value why
--origin-time-scale 25 Knots are in data-frame units; this converts them to seconds and must equal the training rate. The EE counterpart is 10 Hz β€” never copy this flag between the two models. Using 10 here runs the arm at 0.4x speed with no error.
--data-freq 25 Must match the above.
--control-freq 100 Matches YAM_CONTROL_HZ.
--speed-up-times start at 1.0 Velocity scales linearly with it, acceleration quadratically.
--predict-before-end 0.15 (GPU), 0.3 (CPU) Must exceed inference latency or the arm stalls between chunks.

Cameras and gripper

Trained uncropped (RAW 640x480 -> 84x84). The dataset card recommends --crop top_image=42,28,598,414; this model did not use it, and whatever crop you train with must be applied identically at deployment.

Gripper is 0 = open, 1 = closed. Verify on hardware before the first rollout β€” if the convention is reversed the policy opens to grasp and closes to release, which looks almost-working and is hard to diagnose:

cd real_env/yam_teleop && python -c "
from multiprocessing.managers import BaseManager as M
from constants import ARM_RPC_HOST, ARM_RPC_PORT, RPC_AUTHKEY
class Mg(M): pass
Mg.register('YamArm'); m = Mg(address=(ARM_RPC_HOST, ARM_RPC_PORT), authkey=RPC_AUTHKEY); m.connect()
print('gripper reads:', round(float(m.YamArm().get_state()['joint_pos'][6]), 3))"

Expect ~0.0 open, ~1.0 closed.

Data notes

All 200 episodes were kept, including the 2 aborted takes that never close the gripper (198/200 contain a grasp) and the 14 with recovery re-grasps.

Reproducing

python ~/data/yam-200/to_robomimic.py --from lerobot \
  --repo-id Dimios45/yam-pick-duster-200 --root ~/data/yam-200 \
  --output-path data/yam_joint_200.hdf5

cd bspline_policy && python train.py \
  --config-name=yam_pick_joint_200_bspline \
  hydra.run.dir=../outputs/yam_pick_joint_200 \
  training.resume=false logging.mode=offline \
  checkpoint.topk.k=999 dataloader.persistent_workers=True

Citation

@article{han2026b,
  title={B-spline Policy: Accelerating Manipulation Policies via B-spline Action Representations},
  author={Han, Xiaoshen and Xiong, Haoyu and Chen, Haonan and Liu, Chaoqi and
          Torralba, Antonio and Zhu, Yuke and Du, Yilun},
  journal={arXiv preprint arXiv:2607.09648},
  year={2026}
}
Downloads last month

-

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

Dataset used to train Dimios45/yam-pick-duster-200-bspline-joint

Paper for Dimios45/yam-pick-duster-200-bspline-joint