
How Diffusion Policy denoises an action sequence instead of regressing one action: receding horizon control, the Ta trade-off, lerobot defaults, and the real inference cost.
Behaviour cloning in its simplest form is regression: look at the camera, look at the joint angles, predict the next action. It works until the demonstrations disagree with themselves. A human pushing a block can go around it on the left or on the right, and both are correct. Regression averages them and drives straight into the block. Diffusion Policy fixes that by changing what the network outputs: instead of one action, it denoises a whole future action sequence out of Gaussian noise, conditioned on the last couple of camera frames.
This article works through the paper (arXiv 2303.04137), the reference implementation, and the lerobot port you can actually run on an SO-100. It covers what receding horizon control buys you, why the action execution horizon is the one hyperparameter that decides whether the arm is smooth or hesitant, and what the denoising loop costs per control step on real hardware. It also says plainly where AY-Robots does not help: there is no diffusion trainer on this platform.
What you need to know
- •Diffusion Policy predicts an action sequence, not an action. It denoises Tp future steps out of noise with a U-Net or a transformer, conditioned on To observation steps.
- •Receding horizon control: predict Tp, execute only Ta, then re-plan. The paper found Ta = 8 optimal for most tasks; too long and the arm stops reacting, too short and it jitters.
- •Paper numbers: 15 tasks, 4 benchmarks, 46.9% average improvement over the best baseline. Real UR5 Push-T: 95% success against 20% for LSTM-GMM and 0% for IBC.
- •Inference cost is the price. The paper measured 0.1 s per chunk on an Nvidia 3080 with 10 DDIM steps. lerobot's default runs 100 DDPM steps, ten times the network calls.
- •lerobot changed the defaults: horizon 16 / n_action_steps 8 up to v0.5.1, horizon 64 / n_action_steps 32 from v0.6.0 onward. Old tutorials will not match your config.
- •AY-Robots does not train Diffusion Policy. The closest things here are ACT (one forward pass, 20 ms per action step) and GR00T N1.7, which the catalog describes as a VLA foundation model with a diffusion action head.
Checked 23 August 2026. The paper is arXiv 2303.04137, v1 7 March 2023, v5 14 March 2024, which the arXiv comments field labels an extended journal version of the RSS 2023 paper. Every paper number below is read off the v5 PDF. One warning: the v5 PDF abstract and conclusion say 15 tasks across 4 benchmarks, while the arXiv listing page still shows an older abstract saying 12, so the abs page and the PDF disagree. The reference repo real-stanford/diffusion_policy is MIT licensed and its last commit is dated 24 December 2024, so it is stable rather than actively developed. The lerobot port is described against main at version = "0.6.2" in pyproject.toml, newest tag v0.6.1, published 3 August 2026.
The problem single-action regression cannot solve
An imitation learning dataset recorded by teleoperation is not a function. The same observation appears many times with different labels, because the operator made a different choice on Tuesday than on Monday. The paper names three consequences that a plain regression head handles badly, and each of them shows up on a real arm.
| Property of robot action data | What a single-step regression head does | What Diffusion Policy does |
|---|---|---|
| Multimodal demonstrations (left or right around the block) | Predicts the mean of the modes, which is often not a valid action | Samples one mode and commits to it, because the denoiser starts from a random draw |
| Sequential correlation between consecutive actions | Each step is predicted independently, so consecutive steps can be drawn from different modes and the arm jitters | Predicts the whole chunk jointly, so temporal consistency is built in |
| Idle frames where the operator paused | Overfits the pause and the policy gets stuck, which the paper reports for BC-RNN and IBC in real-world runs | A chunk that contains a pause is still one coherent chunk, so the pause does not become an absorbing state |
The earlier fixes were all about the output representation: mixtures of Gaussians, categorical bins over quantised actions, or energy-based implicit policies. Implicit Behavioural Cloning gets multimodality right in theory but needs negative sampling for an intractable normalisation constant, and the paper documents the resulting training instability: its training loss falls smoothly while its action prediction accuracy does not improve, and its evaluation success rate oscillates, which makes checkpoint selection guesswork. For the real Push-T comparison the authors had to select the IBC checkpoint by minimum training-set action MSE instead of by the last checkpoint they used for everything else. Diffusion sidesteps the normalisation constant entirely: it models the score, the gradient of the log density, and the constant differentiates away.
What the denoising loop actually computes
At time step t the policy takes the last To observation steps, calls them Ot, and produces Tp future actions At. Training is the standard DDPM objective with one change: the observation is a condition, not part of the thing being denoised. Add noise to a ground-truth action chunk, ask the network to predict the noise it added, take the MSE. That is the whole loss.
Keeping observations out of the denoised output matters more than it looks. The vision encoder runs once per inference, not once per denoising iteration, which is what makes the method fast enough to close a loop at all. The paper states this explicitly as the reason the formulation accommodates real-time control and makes end-to-end vision training feasible.
# 1. encode observations ONCE -> global conditioning vector
global_cond = encode(state_queue, image_queue) # ResNet18 + spatial softmax
# 2. start from pure noise shaped (batch, horizon, action_dim)
sample = torch.randn(batch_size, config.horizon, action_dim)
# 3. denoise. num_inference_steps is None by default -> num_train_timesteps = 100
noise_scheduler.set_timesteps(num_inference_steps)
for t in noise_scheduler.timesteps:
model_output = unet(sample, t, global_cond=global_cond) # <- one U-Net call
sample = noise_scheduler.step(model_output, t, sample).prev_sample
# 4. keep n_action_steps of the chunk, starting at the CURRENT observation,
# not at the start of the horizon. Everything else is thrown away.
start = config.n_obs_steps - 1
actions = sample[:, start : start + config.n_action_steps]Step 3 is the entire cost story. The U-Net is called once per denoising iteration, and with lerobot's defaults that loop runs 100 times before a single action reaches the servos. Step 4 is the entire behaviour story: most of what the network predicted is discarded on purpose.
| Paper symbol | Meaning | lerobot name | lerobot default (0.6.2) | lerobot default (up to v0.5.1) | Paper value (CNN, most tasks) |
|---|---|---|---|---|---|
| To | Observation horizon, how many past frames condition the denoiser | n_obs_steps | 2 | 2 | 2 |
| Tp | Action prediction horizon, the length of the denoised chunk | horizon | 64 | 16 | 16 |
| Ta | Action execution horizon, how many of those actions actually run | n_action_steps | 32 | 8 | 8 |
| - | Training denoising iterations | num_train_timesteps | 100 | 100 | 100 |
| - | Inference denoising iterations | num_inference_steps | None, meaning 100 | None, meaning 100 | 100 in sim, 16 on the real robot |
| - | Noise schedule | beta_schedule | squaredcos_cap_v2 | squaredcos_cap_v2 | square cosine from iDDPM |

Receding horizon control: predict a lot, commit to a little
The idea is borrowed straight from model predictive control, and the paper cites Mayne and Michalska 1988 for it. Predict a long chunk, execute a short prefix, throw the rest away, re-plan from a fresh observation. You get temporal consistency from the long prediction and reactivity from the short commitment. lerobot's own docstring draws the timeline better than prose does.
(legend: o = n_obs_steps, h = horizon, a = n_action_steps)
|timestep | n-o+1 | n-o+2 | ..... | n | ..... | n+a-1 | n+a | ..... | n-o+h |
|observation is used | YES | YES | YES | YES | NO | NO | NO | NO | NO |
|action is generated | YES | YES | YES | YES | YES | YES | YES | YES | YES |
|action is used | NO | NO | NO | YES | YES | YES | NO | NO | NO |
constraint: n_action_steps <= horizon - n_obs_steps + 1Two things follow. First, the horizon is measured from the first observation, not from now, so with n_obs_steps = 2 you lose a step at the front. Second, this is action chunking with a re-plan rule, which is exactly the family ACT belongs to. The difference is that ACT produces its chunk in one forward pass while Diffusion Policy produces it in a hundred.
The execution horizon is the knob that decides how the arm feels. The paper's ablation (Figure 5, left) shows the trade-off directly and lands on 8 steps for most tasks. At 10 Hz on their UR5 that is 0.8 s of committed motion, or 0.6 s for real Push-T where they used Ta = 6. At lerobot's recording default of 30 fps, the current n_action_steps = 32 commits you to roughly 1.07 s of open-loop motion before the policy looks at a camera again.
If your arm executes a smooth but wrong trajectory into the table, and the failure is always the same shape, suspect n_action_steps before you suspect the weights. Too long and the policy is blind for a second at a time; too short and consecutive chunks are drawn from different modes and the wrist chatters. Change one thing, keep horizon fixed, and re-run. Related symptoms are collected under policy freezes mid-motion and arm twitches then sags.
There is a second, less obvious payoff. Because the chunk extends into the future, a policy that is a few control steps behind reality can still hand over a valid action. The paper measured this: with position control, Diffusion Policy holds peak performance with a simulated latency of up to 4 steps. Velocity control degrades faster, which the authors attribute to compounding error. That is the technical reason the paper insists on end-effector position control rather than velocity commands.
The numbers the paper actually reports
Two backbones are offered for the noise prediction network. A 1D temporal CNN, a U-Net over the time axis adapted from Janner et al., conditioned with FiLM on both the observation embedding and the denoising iteration. And a time-series diffusion transformer built on minGPT, with noisy actions as input tokens and the iteration index as a prepended token. The recommendation in the paper is blunt: start with the CNN, move to the transformer only if the task has fast and sharp action changes, and expect to tune more when you do. Both variants have entries in the arena, so you can read their published benchmark numbers next to the modern VLAs.
| Hyperparameter | CNN Diffusion Policy | Transformer Diffusion Policy |
|---|---|---|
| Denoising network parameters | 256 M on most tasks, 264 M on Transport, 67 M on all four real-robot tasks | 9 M on most tasks, 80 M on Kitchen and real Push-T |
| Vision encoder parameters | 22 M (ResNet-18), 45 M for the two-arm Transport task | same |
| To / Ta / Tp | 2 / 8 / 16 (Block Push is the outlier at 3 / 1 / 12, real Push-T at 2 / 6 / 16) | 2 / 8 / 10, except Push-T, Kitchen and real Push-T at Tp = 16 |
| Learning rate | 1e-4 | 1e-4 |
| Weight decay | 1e-6 | 1e-3 (1e-1 on Push-T) |
| LR schedule | cosine with 500 warmup steps | cosine with 1000 warmup steps |
| Batch size | 256 state-based, 64 image-based | 256 state-based, 64 image-based |
| Denoising iterations, train / eval | 100 / 100 in sim, 100 / 16 on the real robot | 100 / 100 in sim, 100 / 16 on the real robot |
| Tuning behaviour | optimal settings consistent across tasks; bigger is always better | attention dropout and weight decay vary a lot per task; more layers sometimes hurts |
The headline is a 46.9% average relative improvement over the best baseline per task across 15 tasks and 4 benchmarks. That number is a mean of per-task relative improvements, computed as 0.46858, not a success-rate delta, so read it as a ranking claim rather than an absolute one. The long-horizon results are more interesting: 32% improvement on the Block Push two-block metric and 213% on the Franka Kitchen four-object metric, both cases where the order of sub-goals is arbitrary and a policy that cannot commit falls apart. None of it is a claim about how many training steps you personally will need, which depends on your data.
- Real UR5 Push-T, 136 demonstration episodes, a fixed 12 hours of training per method: 95% success and 0.8 average IoU against 0.84 for the human operator. The best IBC variant scored 0% and the best LSTM-GMM 20%.
- Sauce pouring and spreading on a Franka: 50 demonstrations per task, 90% used for training, 1000 epochs, last checkpoint evaluated.
- Bimanual shirt folding: 284 demonstrations, 75% success over 20 trials. Reported failure modes are missed grasps on the sleeves and collar, and the policy being unable to stop adjusting the shirt at the end.
- Vision encoder ablation on robomimic square (Table 5), CNN backbone, 500 epochs, 50 initial conditions. The full numbers are in the callout below; the short version is that finetuning a pretrained encoder at a lower learning rate wins in all three architectures tested.
- Real-robot loop on the UR5 station: the policy commands at 10 Hz, the controller linearly interpolates to 125 Hz. Five RealSense D415 cameras record the scene, two of them feed the policy, downsampled to 320x240 at 10 fps. Demonstrations came from a 3Dconnexion SpaceMouse at 10 Hz.
- Training length in the reference repo's real-robot config: 600 epochs, AdamW at lr 1e-4 with betas 0.95 and 0.999, weight decay 1e-6, EMA enabled, a checkpoint every 50 epochs. The config comments say EMA destroys performance with BatchNorm, which is why the encoder uses GroupNorm.
Table 5 of the paper is easy to miss and expensive to rediscover. On robomimic square (ph) with the CNN backbone: ResNet-18 scores 0.94 from scratch, 0.58 frozen, 0.92 finetuned; ResNet-34 scores 0.92, 0.40, 0.94; a CLIP-trained ViT-B/16 scores 0.22, 0.70, 0.98. So freezing is the worst option for both ResNets, training from scratch is the worst option for the ViT, and finetuning the pretrained encoder at a learning rate 10x below the policy network is best in all three rows. lerobot's default sits in the middle: pretrained_backbone_weights = "ResNet18_Weights.IMAGENET1K_V1", trained end-to-end, never frozen.
What it costs at inference on a real arm
The paper is honest about this in its limitations section: Diffusion Policy has higher computational cost and higher inference latency than simpler methods like LSTM-GMM, and action sequence prediction only partially mitigates it. The one hard measurement they give is 0.1 s per chunk on an Nvidia 3080, using DDIM with 100 training iterations and 10 inference iterations.
| Configuration | U-Net calls per chunk | Where it comes from |
|---|---|---|
| Paper, simulation benchmarks | 100 | iDDPM, same iteration count for training and inference (Tables 7 and 8) |
| Paper, real robot, Tables 7 and 8 | 16 | DDIM, D-Iters Eval is 16 for every real task in both tables |
| Paper, section 3.4, the quoted 0.1 s on a 3080 | 10 | DDIM. The paper contradicts itself here: section 3.4 says the real-world experiments used 10 inference iterations, the appendix tables say 16 |
| Reference repo, train_diffusion_unet_real_image_workspace.yaml | 100 | DDIMScheduler is selected and then num_inference_steps is set to 100 |
| lerobot main 0.6.2, out of the box | 100 | DDPM, num_inference_steps is None so it falls back to num_train_timesteps |
In lerobot, num_inference_steps: int | None = None, and DiffusionModel.__init__ resolves None to num_train_timesteps, which is 100. Nothing warns you. You get a policy that runs ten times more network calls per action chunk than the fastest configuration the paper published. Switch to --policy.noise_scheduler_type=DDIM --policy.num_inference_steps=10 and measure the success rate before and after, on the same checkpoint. The shipped config in the reference repo has the same shape: it selects DDIMScheduler and then sets num_inference_steps: 100, so it never collects the speedup DDIM exists for.
- Handles multimodal demonstrations without mode collapse or averaging, which is a common reason a regressed policy drives into the obstacle it was supposed to go around.
- Trains stably. The paper reports optimal hyperparameters that are mostly consistent across tasks for the CNN variant, in explicit contrast to IBC.
- Scales to high-dimensional outputs, so predicting 16 or 64 steps jointly is normal rather than exotic.
- Receding horizon gives real latency tolerance: peak performance up to 4 steps of delay with position control.
- Reference implementation is MIT licensed, and the lerobot port is a registered policy type you train with the same lerobot-train entry point as ACT or SmolVLA.
- Inference cost scales linearly with denoising iterations. At lerobot's default of 100 it is 100 U-Net forward passes per chunk, against one forward pass for a single-pass chunk policy like ACT.
- No language conditioning. The lerobot diffusion config takes state plus images plus optional environment state, and nothing else. One checkpoint per task.
- No pretrained base model to fine-tune from. The vision backbone can start from ImageNet weights, the denoiser starts from random weights on every new task, exactly like ACT.
- The CNN backbone smooths high-frequency action signals, which the paper attributes to the inductive bias of temporal convolutions. Fast, sharp motions need the transformer and more tuning.
- Inherits every limitation of behaviour cloning. Bad demonstrations produce a bad policy, and there is no reward signal to notice.
Running it yourself with lerobot on an SO-100
The reference repo targets a UR5 with RealSense cameras and a SpaceMouse. For a low-cost arm the practical route is lerobot's port, which registers Diffusion Policy under the config name diffusion and trains it with the same lerobot-train entry point as ACT or SmolVLA. Record first, then train, then roll out.
- 1Install lerobot with the diffusion extra
Diffusion Policy depends on Hugging Face diffusers for its schedulers, which is an optional extra:
_make_noise_schedulercallsrequire_package("diffusers", extra="diffusion"), so without it the policy raises at construction time. On main the pin isdiffusers>=0.38.0,<0.40.0. The recording and rollout CLIs live behind a second extra,core_scripts.bashgit clone https://github.com/huggingface/lerobot.git cd lerobot pip install -e ".[diffusion,core_scripts]" lerobot-train --help - 2Record a dataset from teleoperation
Defaults on main:
fps=30,episode_time_s=60,reset_time_s=60,num_episodes=50. Vary the start pose every episode. A diffusion policy is exactly as multimodal as your demonstrations and no more.bashlerobot-record \ --robot.type=so100_follower \ --robot.port=/dev/tty.usbmodem58760431541 \ --robot.id=black \ --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ --teleop.type=so100_leader \ --teleop.port=/dev/tty.usbmodem58760431551 \ --teleop.id=blue \ --dataset.repo_id=${HF_USER}/so100_pickplace \ --dataset.num_episodes=50 \ --dataset.single_task="Pick up the cube and drop it in the bin" \ --display_data=true - 3Train with the diffusion policy type
lerobot's global defaults are
seed=1000,batch_size=8,steps=100000,save_freq=20000. Batch 8 is small for this policy; the paper used 64 for image-based tasks and the publishedlerobot/diffusion_pushtcheckpoint used 64 as well.bashlerobot-train \ --dataset.repo_id=${HF_USER}/so100_pickplace \ --policy.type=diffusion \ --policy.device=cuda \ --batch_size=64 \ --steps=200000 \ --output_dir=outputs/train/so100_diffusion \ --job_name=so100_diffusion \ --wandb.enable=true - 4Set the horizons explicitly instead of inheriting them
The defaults changed between v0.5.1 and v0.6.0. Pin them in the command so your run is reproducible against a written-down number rather than against whatever
maindid last month.bash# the pre-0.6.0 defaults, which match the paper --policy.horizon=16 \ --policy.n_action_steps=8 \ --policy.n_obs_steps=2 \ --policy.drop_n_last_frames=7 # __post_init__ validates exactly one of these two rules: # horizon % (2 ** len(down_dims)) == 0, i.e. a multiple of 8 # the other one is only written down in the select_action docstring: # n_action_steps <= horizon - n_obs_steps + 1 <- nothing checks this - 5Cut the denoising steps and measure what it costs you
Train with DDPM at 100 steps, then evaluate the same checkpoint at several inference budgets. DDIM decouples training and inference iteration counts, which is the whole reason it is in the config.
bash--policy.noise_scheduler_type=DDIM \ --policy.num_inference_steps=10 - 6Roll it out on the arm with lerobot-rollout
This is the step most old tutorials get wrong. On lerobot main
lerobot-recordis a pure data-collection tool with no policy inference: it has no--policy.pathfield, and it rejects dataset names starting witheval_with an error telling you to uselerobot-rollout. The rollout CLI takes a--strategy.typeofbase,sentry,highlight,daggerorepisodic;episodicis the one with reset phases between episodes. Keep a hand on the power switch for the first episode.bashlerobot-rollout \ --strategy.type=episodic \ --policy.path=outputs/train/so100_diffusion/checkpoints/last/pretrained_model \ --robot.type=so100_follower \ --robot.port=/dev/tty.usbmodem58760431541 \ --robot.id=black \ --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ --dataset.repo_id=${HF_USER}/rollout_so100_diffusion \ --dataset.num_episodes=10 \ --dataset.single_task="Pick up the cube and drop it in the bin"
In configuration_diffusion.py on main the line reads drop_n_last_frames: int = 7 # horizon - n_action_steps - n_obs_steps + 1. With the old defaults that formula gave 16 - 8 - 2 + 1 = 7 and the comment was correct. With the current defaults it gives 64 - 32 - 2 + 1 = 31, and the constant was not updated. If you change horizon or n_action_steps, recompute drop_n_last_frames yourself and pass it explicitly. Nothing in the config validates it.
For a sanity check that the pipeline works before you spend a day on your own data, the published lerobot/diffusion_pusht checkpoint was trained on the lerobot/pusht dataset with --batch_size=64 --steps=200000, and the weights that are actually published are the checkpoint at 175k steps. Its model card reports 65.4% success over 500 evaluation episodes with an average maximum overlap ratio of 0.955, against 64.2% and 0.957 for an equivalent model trained in the original repo. Note that the card's commands still use the old python lerobot/scripts/train.py form, which predates the console entry points. Translate them before you paste.
Everything above, on your own hardware. You own the GPU, the CUDA install, the diffusers pin, the camera indices and the checkpoint directory. This is the right path if you specifically want Diffusion Policy, because it is the only path that gives you one.
- Build an SO-100 and calibrate it. Wiring, servo IDs and the leader-follower pairing are covered in the SO-100 setup guide.
- Record 50 or more episodes with
lerobot-record, varying the start pose every time. pip install -e ".[diffusion,core_scripts]"and train with--policy.type=diffusion.- Pin
horizon,n_action_steps,n_obs_stepsanddrop_n_last_framesyourself rather than inheriting them. - Sweep
num_inference_stepswith DDIM and record success rate against latency. - Roll out with
lerobot-rollout, on the same machine as the arm.
The real cost is calendar time, not money. The paper allowed a fixed 12 hours of training per method on its real Push-T comparison, and the published lerobot Push-T run was launched with --steps=200000 and published at the 175k checkpoint. Budget an afternoon of training and a second afternoon of rollouts. If the U-Net does not fit, --policy.gradient_checkpointing=true recomputes the residual blocks in the backward pass and trades speed for activation memory; the config default is False.
AY-Robots trains five policies: GR00T N1.7, GR00T N1.5, Pi0.5, SmolVLA and ACT. Diffusion Policy is not one of them, and there is no trainer key for it. If you came here to train a Diffusion Policy checkpoint, use the manual path in the other tab.
What the platform does help with is everything around the policy, plus two models that are close relatives. The AY-Robots catalog describes GR00T N1.7 as a VLA foundation model with a diffusion action head, so the denoising idea is doing its work inside a 3 B parameter VLA rather than inside a 256 M U-Net you trained yourself. ACT is the honest control condition: the same action chunking, the same from-scratch training, one forward pass instead of a hundred.
| Job | How it works here | Numbers |
|---|---|---|
| Record the dataset | The desktop client records LeRobot-format episodes, camera streams and joint states straight from a teleop session | LeRobot v2.0/v2.1 for GR00T, v3.0 for Pi0.5, SmolVLA and ACT |
| Train the closest analogue | ACT on SO-100: chunked actions, no denoising, no language | chunkSize 100, nActionSteps 100, batch 8, lr 1e-5, 100000 steps |
| Train the VLA with the diffusion action head | GR00T N1.7 on SO-100 on a rented A100 or H100 | batch 32, lr 1e-4, 20000 steps, about 4 to 12 USD per run |
| Compare published results | Arena lists Diffusion Policy CNN and Diffusion Policy Transformer alongside the VLAs | 85 models, 332 benchmark results, each value linked to its source |
| Serve a policy without a local GPU | /api/inference/pod provisions a cloud GPU pod with an idle watchdog | 20 to 485 ms per action step depending on the model |
If you want the denoising formulation on your arm, lerobot is the tool and this platform is not. If what you want is a policy that works on your SO-100 without owning a GPU, the five trainable policies and their measured latency are a better starting point than reimplementing a 2023 paper.
Where Diffusion Policy sits next to what you can train today
Three families descend from the same observation that a robot policy should output a sequence. ACT predicts a chunk in one pass with a transformer and a CVAE. Diffusion Policy denoises the chunk over many passes. Flow matching policies like Pi0.5 learn a continuous velocity field to the same end with far fewer integration steps, which is why the family moved in that direction. The platform numbers below come from the AY-Robots catalog; the Diffusion Policy row comes from the paper.
| Policy | Action representation | Latency per action step | Language input | Base checkpoint in the catalog | Trainable on AY-Robots |
|---|---|---|---|---|---|
| Diffusion Policy (paper) | Denoised chunk, Tp = 16, Ta = 8 | 0.1 s per chunk on a 3080 at 10 DDIM steps; 100 U-Net calls per chunk with lerobot's defaults | no | none, train from scratch | no |
| ACT | Chunk of 100 actions from one forward pass | 20 ms | no | none, ACT only exists after training on your task | yes |
| SmolVLA | Compact VLA, ~450 M params | 245 ms | yes | not listed | yes |
| GR00T N1.7 | VLA foundation model with a diffusion action head, ~3 B params, ~40 M of them trained during fine-tuning | 152 ms | yes | nvidia/GR00T-N1.7-3B | yes |
| Pi0.5 | Flow-matching VLA, ~3 B params, PaliGemma backbone | 485 ms | yes | lerobot/pi05_base | yes |

Read the latency column and the action-representation column together. The 20 ms, 152 ms, 245 ms and 485 ms figures are the platform's own measured per-action-step numbers for the models it trains. Diffusion Policy has no row there because it is not trained here, and its cost has a different shape: one U-Net call multiplied by num_inference_steps, per chunk, before any action reaches the servos. That is the entire trade. You pay N forward passes to buy a properly multimodal action distribution, and whether the purchase is worth it depends on whether your demonstrations are actually multimodal, which is a property of how you recorded them rather than of the model. The data collection guide covers how to record demonstrations that are varied without being inconsistent.
Honest limits, including on this platform
Two of these are properties of the method and two are properties of where you run it. None of them are fixable by tuning.
- No language, no multi-task. lerobot's diffusion config accepts
observation.stateplus at least oneobservation.image*key orobservation.environment_state. There is no task string. One checkpoint per task, exactly like ACT, and unlike every VLA on this platform. - No pretraining to borrow. There is no diffusion policy foundation checkpoint to fine-tune. The vision encoder can start from ImageNet weights, the denoiser cannot start from anything.
- Inference latency is the deployment constraint. Remote inference over the public internet is viable for slow pick-and-place and not for fast reactive motion, whatever the model. Adding round trips to a policy that already spends 100 U-Net calls per chunk makes it worse, not better.
- AY-Robots will not train this for you. The five trainable policies are fixed. What the platform contributes to a diffusion project is the arm, the teleoperation, the recording client and the public dataset directory, not the trainer.
The control loop on this platform runs 20 to 485 ms per action step depending on the model, and a diffusion policy at 100 denoising iterations sits at the slow end of that range or past it. Put the inference process on the same machine as the arm, or accept that the arm will hesitate. This is a physics-and-networking limit, not a configuration you can tune around. Latency symptoms and their causes are collected under the failure-mode pages.
Five policies, real latency numbers, no guessing
Diffusion Policy is not one of the models you can train here. The five that are come with measured inference latency, minimum episode counts, GPU tier and cost per run, so you can pick one before you record 50 episodes for the wrong thing.
Compare the five policiesWhat came after, and what to read next
The paper's own limitations section points at the fix: fewer inference steps through better noise schedules, better solvers, or consistency models. That work happened. Consistency Policy (Prasad, Lin, Wu, Zhou and Bohg, May 2024) distills a trained Diffusion Policy by enforcing self-consistency along its own denoising trajectories, and reports an order of magnitude speedup over the fastest alternative method while demonstrating inference on a laptop GPU. 3D Diffusion Policy (Ze et al., March 2024) swaps the image encoder for a point-cloud encoder and reports 85% success on four real tasks from 40 demonstrations each. Universal Manipulation Interface (Chi et al., February 2024, the same first author) attacks the data side instead, collecting demonstrations with a handheld gripper rather than a robot.
The larger movement was toward flow matching, which reaches a multimodal action distribution with a straighter path and far fewer integration steps than a 100-step denoising loop. That is what Pi0 and Pi0.5 do. If you are choosing a model to deploy rather than to understand, start from the VLA overview, then the ACT versus SmolVLA comparison for the cheap end and the pricing page for what a run costs. If you have no arm yet, /live streams a physical SO-100 with no signup.

Is Diffusion Policy still worth using in 2026?▾
As a method to understand, yes: receding horizon action prediction is the structural idea that ACT, Pi0.5 and GR00T all inherited. As a thing to deploy on a new project, the honest answer is that flow-matching policies reach a comparable action distribution with far fewer network calls per chunk, and the reference repo has not received a commit since 24 December 2024. Use it when you want a single-task visuomotor policy with genuinely multimodal demonstrations and no language requirement.
What is the difference between horizon and n_action_steps?▾
horizon is how many future actions the network denoises, called Tp in the paper. n_action_steps is how many of those actually run on the robot before the policy re-plans, called Ta. lerobot validates only one rule in DiffusionConfig.__post_init__: horizon must be a multiple of 2 ** len(down_dims), which is 8 with the default down_dims of (512, 1024, 2048). The second rule, n_action_steps <= horizon - n_obs_steps + 1, is written down in the select_action docstring but nothing checks it, so breaking it silently gives you a shorter chunk than you asked for. The paper found Ta = 8 optimal for most tasks.
Why is my diffusion policy so slow at inference?▾
Almost certainly because num_inference_steps is None, which lerobot resolves to num_train_timesteps, which is 100. Every action chunk costs 100 U-Net forward passes. Set noise_scheduler_type to DDIM and num_inference_steps to 10 or 16, then re-evaluate the same checkpoint to see what accuracy you traded away. The paper is inconsistent about its own number: the appendix tables list 16 inference iterations for every real-robot task, while section 3.4 says 10 iterations and quotes 0.1 s per chunk on an Nvidia 3080.
Can I train Diffusion Policy on AY-Robots?▾
No. The platform trains GR00T N1.7, GR00T N1.5, Pi0.5, SmolVLA and ACT, and there is no diffusion trainer key. You can record the dataset here with the desktop client and then train Diffusion Policy yourself with lerobot, or train ACT instead, which is the same action-chunking idea at one forward pass and 20 ms per action step.
Does Diffusion Policy take a language instruction?▾
No. The lerobot diffusion config takes proprioceptive state plus one or more camera images, or an environment state vector. There is no task text field. If you need one checkpoint that responds to different instructions, you need a vision-language-action model such as SmolVLA, Pi0.5 or GR00T N1.7.
CNN or transformer backbone?▾
The paper recommends starting with the CNN-based implementation on a new task. Its optimal hyperparameters were consistent across tasks and increasing model size always helped. The transformer achieved most of the best state-based results, especially where task complexity and rate of action change are high, but it is more sensitive to hyperparameters: optimal attention dropout and weight decay varied greatly between tasks and adding layers sometimes hurt. lerobot's port implements only the CNN U-Net variant, and its vision_backbone validator rejects anything that is not a ResNet.
Sources
- Diffusion Policy: Visuomotor Policy Learning via Action Diffusion (Chi, Xu, Feng, Cousineau, Du, Burchfiel, Tedrake, Song), arXiv v5, 14 March 2024
- Diffusion Policy project page, code, data and training logs
- RSS 2023 program entry for the original conference paper
- real-stanford/diffusion_policy, the reference implementation (MIT, last commit 24 December 2024)
- train_diffusion_unet_real_image_workspace.yaml, the shipped real-robot training config
- lerobot DiffusionConfig on main (0.6.2), all defaults and validation rules
- lerobot DiffusionPolicy: select_action, conditional_sample and the noise scheduler factory
- lerobot v0.6.1 release, 3 August 2026
- lerobot/diffusion_pusht model card: training command and 500-episode evaluation
- lerobot-rollout, the policy deployment CLI on main, with its strategy and inference options
- Denoising Diffusion Probabilistic Models (Ho, Jain, Abbeel), June 2020
- Denoising Diffusion Implicit Models (Song, Meng, Ermon), October 2020
- Improved Denoising Diffusion Probabilistic Models (Nichol, Dhariwal), February 2021, source of the square cosine schedule
- Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware (ACT / ALOHA), April 2023
- Consistency Policy: Accelerated Visuomotor Policies via Consistency Distillation, May 2024
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started