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.
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.
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.
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:
1@article{han2026b,
2 title={B-spline Policy: Accelerating Manipulation Policies via B-spline Action Representations},
3 author={Han, Xiaoshen and Xiong, Haoyu and Chen, Haonan and Liu, Chaoqi and
4 Torralba, Antonio and Zhu, Yuke and Du, Yilun},
5 journal={arXiv preprint arXiv:2607.09648},
6 year={2026}
7}