
A step is not a unit of progress. How to turn published step counts for GR00T, SmolVLA, ACT and Pi0.5 into passes over your own dataset, and when to stop the run.
Every training form has a field called max steps, and almost nobody changes it. That number decides what a run costs, how long you wait, and whether the policy that comes out has memorised your demonstrations or learned the task. On its own it tells you nothing. A training step is one gradient update over one batch, so 20000 steps at batch 32 and 20000 steps at batch 1 are two completely different runs on the same screen.
This article converts the published fine-tuning step counts for GR00T, SmolVLA, ACT and Pi0.5 into a unit that transfers between them, works out how run length should scale with the number of episodes you recorded, and gives a stopping rule that does not need a validation loss. Upstream numbers were checked against the main branches in August 2026.
What you need to know
- •A step is one gradient update over one batch. Step counts are not comparable across models until you multiply by batch size.
- •Passes over the data (samples divided by frames) is the unit that transfers. LeRobot computes it and logs it as epochs on every line.
- •Epoch means one sample per episode in the original ACT repo and one sample per frame in LeRobot, a factor of several hundred.
- •On a 50-episode SO-100 dataset the trainer defaults here range from about 1 pass (Pi0.5) to about 27 (ACT). Same form, very different runs.
- •The ACT authors report success rate improving after the loss plateaus. The robomimic study found the lowest-validation-loss checkpoint is often a bad policy.
- •Stop by saving checkpoints often and evaluating several on the arm, not by watching the loss flatten.
A step is not a unit of progress
One step consumes one batch. The number of training samples a run has seen is therefore steps multiplied by batch size, multiplied again by gradient accumulation where the trainer actually implements it. Divide that by the number of frames in your LeRobot dataset and you get the number of passes over the data. That is the number worth comparing: it stays meaningful when batch size changes by a factor of 32 between two models on the same page.
LeRobot does this arithmetic for you and nobody looks at it. The metrics tracker keeps a running epoch count, and the training script prints the three inputs at startup, so the ratio is on screen before the first gradient lands.
# src/lerobot/utils/logging_utils.py - MetricsTracker
self.samples = self.steps * self._batch_size * self._dp_world_size
self.episodes = self.samples / self._avg_samples_per_ep
self.epochs = self.samples / self._num_frames
# src/lerobot/scripts/lerobot_train.py - printed once at startup
logging.info(f"{cfg.steps=} ({format_big_number(cfg.steps)})")
logging.info(f"{dataset.num_frames=} ({format_big_number(dataset.num_frames)})")
logging.info(f"{dataset.num_episodes=}")samples = steps x batch_size x gradient_accumulationpasses over the data = samples / num_frames
Every table below is that arithmetic applied to a published recipe. Where a trainer ignores the accumulation value it was given, the multiplier is 1 whatever the form says.
The word epoch means two different things
First, one trap. In the original ACT repository from the ALOHA paper, the dataset returns one item per episode and picks a random start timestep inside it. In LeRobot it returns one item per frame. Both call a full sweep an epoch, and the two differ by roughly the number of frames in an episode.
# tonyzhaozh/act - utils.py
class EpisodicDataset(torch.utils.data.Dataset):
def __len__(self):
return len(self.episode_ids) # one item per EPISODE
def __getitem__(self, index):
...
start_ts = np.random.choice(episode_len) # random window inside it
# huggingface/lerobot - datasets/lerobot_dataset.py
class LeRobotDataset(torch.utils.data.Dataset):
def __len__(self):
"""Return the number of frames in the selected episodes."""
return self.num_frames # one item per FRAME| Codebase | One epoch is | On 50 episodes of 600 frames | Optimizer steps at batch 8 |
|---|---|---|---|
| tonyzhaozh/act | one random window per episode | 50 samples | about 6 |
| huggingface/lerobot | one sample per frame | 30000 samples | 3750 |
So the ACT README command with --num_epochs 2000 is 100000 samples, about 12500 optimizer steps at batch 8, and about 3.3 passes over a 30000-frame dataset. Their advice for real hardware, at least 5000 epochs, is about 8 passes. LeRobot's own default for ACT is 100000 steps at batch 8: 800000 samples, about 27 passes. Same architecture, same task size, an eightfold difference, both shipped as sensible.
What the upstream projects actually publish
Here is every published recipe for the five policies this platform trains, converted to samples and then to passes over one hypothetical dataset: 50 episodes on an SO-100 at 30 fps, 20 seconds each, so 30000 frames. That is the yardstick below; your own frame count will differ and you should use it instead.
| Recipe | What it specifies | Samples | Passes over 30000 frames |
|---|---|---|---|
| ACT README, simulation example | --num_epochs 2000, batch 8 | 100000 | about 3.3 |
| ACT README, real-world advice | at least 5000 epochs | at least 250000 | about 8.3 |
| LeRobot TrainPipelineConfig default | steps 100000, batch 8 | 800000 | about 27 |
| SmolVLA docs, fine-tune example | --steps=20000 --batch_size=64 | 1280000 | about 43 |
| SmolVLA model card example | --steps=100000 --batch_size=4 | 400000 | about 13 |
| openpi TrainConfig default | num_train_steps 30000, batch 32 | 960000 | about 32 |
| openpi pi05_droid_finetune | num_train_steps 20000, batch 32 | 640000 | about 21 |
| Isaac-GR00T README example | --max-steps 2000 --global-batch-size 32 | 64000 | about 2.1 |
| Isaac-GR00T FinetuneConfig default | max_steps 10000, global_batch_size 64 | 640000 | about 21 |
The spread is 2 to 43 passes and every row is a shipped default or a documented recommendation. There is no consensus number, and the projects say so themselves. The Isaac-GR00T training tips say to maximize batch size for your hardware and train for a few thousand steps, and that 2000-step example runs against a five-episode demo dataset of 4148 frames, so it is about 15 passes over that data rather than 2 over yours. The SmolVLA documentation says 20k steps takes roughly 4 hours on a single A100 and that you should tune the number to your use case. The SmolVLA paper fine-tunes for 200000 steps on real-world tasks, then adds that in practice far fewer steps sacrifice no significant performance.
One thing the spread does explain: models that start from a pretrained vision-language-action model checkpoint already know how to look at a scene and produce an action chunk, so the fine-tune only attaches your embodiment and task. ACT starts from nothing, with no base model at all, and learns the whole mapping from your 30000 frames, which is why its published run lengths sit at the high end. The policy comparison page shows which of the five have a vendor base checkpoint.

What this platform sends by default
The training backend sends a fixed set of hyperparameters per policy unless you override them. Run the same arithmetic on those defaults and the picture is uncomfortable.
| Policy | max steps | batch size | gradient accumulation | samples | passes over 30000 frames |
|---|---|---|---|---|---|
| GR00T N1.7 | 20000 | 32 | 1, applied | 640000 | about 21 |
| GR00T N1.5 | 2000 | 1 | 16, applied | 32000 | about 1.1 |
| Pi0.5 | 30000 | 1 | 16, not applied | 30000 | about 1.0 |
| SmolVLA | 20000 | 2 | 8, not applied | 40000 | about 1.3 |
| ACT | 100000 | 8 | 1 | 800000 | about 27 |
The form carries a gradient accumulation of 16 for Pi0.5 and 8 for SmolVLA, and neither is applied: lerobot 0.5.1 has no such flag. Effective batch stays at 1 and 2. If you sized a step count assuming accumulation was real, the run sees sixteen or eight times fewer samples than planned. Raise max steps to compensate, or raise the batch size if the card has the memory. If it does not, see out of memory during training.
Read the table row by row. GR00T N1.7 at batch 32 and ACT at batch 8 both get tens of passes, which is a regime where a small dataset can be memorised. Pi0.5 and GR00T N1.5 get roughly one pass, which for a pretrained VLA is not absurd but sits at the floor. The step counts match the upstream recipes; the batch sizes were chosen to fit memory, and nobody rescaled the steps afterwards. The default is a starting point, not a recommendation.

Scaling run length with dataset size
The rule follows from the formula: hold passes roughly constant and let steps scale with frames. Double the dataset at a fixed step count and you halve how often the model sees each frame. Double the batch instead and you double it. Neither is a decision you want to make by accident.
- 1Count frames, not episodes
A 50-episode dataset can be 20000 or 60000 frames depending on task length and whether reset phases were trimmed. Ask the dataset, do not estimate.
pythonfrom lerobot.datasets.lerobot_dataset import LeRobotDataset ds = LeRobotDataset("your-user/so100-pick-place") print("episodes:", ds.num_episodes) print("frames: ", ds.num_frames) print("avg frames/episode:", ds.num_frames / ds.num_episodes) - 2Pick a target number of passes
From the published recipes: 10 to 30 passes for a pretrained VLA, 20 to 40 for ACT from scratch. Start at 20. That bracket comes from other people's shipped defaults, not from a law.
- 3Convert to steps
steps = passes x frames / (batch x accumulation). Use the effective batch, which for Pi0.5 and SmolVLA here means ignoring the accumulation value entirely.
bashFRAMES=30000 BATCH=8 PASSES=20 echo $(( FRAMES * PASSES / BATCH )) # 75000 steps - 4Set the checkpoint interval to give yourself choices
Divide the step count by 8 to 10. The LeRobot default of save_freq 20000 against a 20000-step run leaves one checkpoint, which makes the stopping question unanswerable.
bash# 75000 steps, a checkpoint roughly every 7500 --steps=75000 --save_freq=7500 - 5Launch and watch the epoch counter, not the loss
The first log line tells you whether the arithmetic was right. If it ends at epochs 0.3, the run was too short regardless of what the loss did.
bashlerobot-train \ --policy.path=lerobot/smolvla_base \ --dataset.repo_id=your-user/so100-pick-place \ --batch_size=8 \ --steps=75000 \ --save_freq=7500 \ --output_dir=outputs/train/smolvla_pickplace \ --job_name=smolvla_pickplace \ --policy.device=cuda \ --wandb.enable=true
| Episodes at 20 s, 30 fps | Frames | Steps for 20 passes at batch 2 | at batch 8 | at batch 32 |
|---|---|---|---|---|
| 30 | 18000 | 180000 | 45000 | 11250 |
| 50 | 30000 | 300000 | 75000 | 18750 |
| 100 | 60000 | 600000 | 150000 | 37500 |
| 200 | 120000 | 1200000 | 300000 | 75000 |
LeRobot's CosineDecayWithWarmupSchedulerConfig, used by Pi0.5 and SmolVLA, auto-scales. The class itself takes num_warmup_steps and num_decay_steps as required arguments; the values come from the policy config, where scheduler_warmup_steps is 1000 and scheduler_decay_steps is 30000. Below that decay length both are rescaled by the ratio, so --steps=3000 gives 100 warmup steps and a 3000-step decay. GR00T uses warmup_ratio (default 0.05), proportional to max steps by construction. ACT has no scheduler: get_scheduler_preset() returns None, so the learning rate stays at 1e-5 throughout and the final checkpoint has no special status.
What overfitting looks like on a small task
With 30 to 50 episodes of one task there is not much to learn and a 3 billion parameter model can memorise most of it. Imitation learning gives no warning, because the loss is measured against the demonstrations you already have. Here is what it looks like from the outside.
- The training loss keeps falling and the robot gets worse. Nothing in the trainer output contradicts it.
- The arm runs the demonstrated trajectory regardless of where the object is. Move the cube 10 cm and it still goes to the mean of the training positions.
- The gripper closes on time rather than on sight, because timing was easier to memorise than the visual cue.
- Success from the trained start pose stays high while a new pose collapses, the pattern behind policy only works in one setup.
- The motion is confident and wrong, which is harder to debug than hesitant and wrong. The opposite failure is loss falls but the policy does nothing.
The counterweight is that stopping early is also wrong. The ACT README opens by saying that if the policy is jerky or pauses mid-episode you should just train for longer, because success rate and smoothness can improve well after the loss plateaus. The robomimic study goes further: the checkpoint with the lowest validation loss was frequently far worse than the best one, in one case 2.7 percent success against 80.7 percent on the same task, and success rate kept climbing while validation loss increased substantially. Loss is a poor selector in both directions.
- The ACT authors recommend it for real hardware: at least 5000 epochs, or 3 to 4 times the length after the loss has plateaued.
- More checkpoints inside the useful range, so one bad checkpoint does not end the run.
- On the RTX 4090 tier a longer run is cheap: 2 to 5 hours at 0.30 to 0.60 USD per hour, about 1 to 3 USD.
- Past scheduler_decay_steps the Pi0.5 and SmolVLA cosine schedule is clamped, so every step beyond 30000 runs at the 2.5e-6 floor rather than decaying further. Cheap fine-tuning at a low learning rate, not a wasted tail.
- Cost and wall clock scale linearly. On the A100 80 GB or H100 tier a run is 3 to 6 hours at 1.20 to 2.00 USD per hour, about 4 to 12 USD, and doubling steps doubles that.
- On a 30 to 50 episode dataset most of the extra passes memorise positions rather than learn the task.
- GR00T keeps only save_total_limit checkpoints, default 5, so a long run with frequent saves quietly deletes the early ones.
- Every extra checkpoint has to be evaluated on the arm. Human time, not GPU time, is what a long run really costs.
When to stop
There is no free stopping signal here. LeRobot does support a held-out loss: eval_steps computes loss on held-out episodes every N steps, but it defaults to 0 and errors unless dataset.eval_split is non-zero. The curve is informative, but the robomimic result means you should not select a checkpoint by it. The GR00T fine-tune path computes no validation loss at all.
So the stopping rule is behavioural. Save often, evaluate several checkpoints on the real arm, and stop when two consecutive ones fail to beat the best you have. More work than watching a curve, and the only thing that measures what you care about.
- 1Turn on the held-out loss, for information only
A 10 percent split costs five episodes out of fifty. Worth it above 100 episodes, arguable below.
bash--dataset.eval_split=0.1 --eval_steps=1000 - 2Save 8 to 10 checkpoints across the run
Set save_freq to steps divided by 10. On GR00T set save_steps the same way and raise save_total_limit above the checkpoint count you expect, or the early ones are deleted.
bash# lerobot: 75000 steps --save_freq=7500 # Isaac-GR00T FinetuneConfig fields (defaults 1000 and 5) # save_steps, save_total_limit - 3Evaluate at least three checkpoints on the arm
The last one, one at about 60 percent of the run, one at about 30 percent. Ten to twenty rollouts each from varied start positions, not the demonstrated one.
bashlerobot-rollout \ --strategy.type=base \ --policy.path=outputs/train/smolvla_pickplace/checkpoints/045000/pretrained_model \ --robot.type=so100_follower \ --robot.port=/dev/ttyACM1 \ --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \ --task="Put the lego brick in the box" \ --duration=60 - 4Decide from the rollouts
If the last checkpoint is best, the run was probably too short: extend it rather than shipping the edge of the curve. If a middle checkpoint wins, you have found the length for this dataset.
- 5Write the number down with the dataset size
The transferable quantity is passes, not steps. Record steps, batch size and frame count together, or the number is useless on the next dataset.
LeRobot's save_freq defaults to 20000, so a 20000-step run produces exactly one checkpoint and there is nothing to compare. Isaac-GR00T's save_total_limit defaults to 5 with save_steps at 1000, so a 20000-step run writes 20 checkpoints and keeps the last 5. Both are reasonable for disk usage and hostile to checkpoint selection. Change them before the run, not after.
Doing it by hand, or on this platform
Everything above is public. Clone LeRobot, count your frames, compute the step count, run it on a card you own or rent. The work is the arithmetic and the evaluation, not the tooling.
git clone https://github.com/huggingface/lerobot.git
cd lerobot && pip install -e ".[smolvla]"
# 1. what is actually in the dataset
python -c "
from lerobot.datasets.lerobot_dataset import LeRobotDataset
d = LeRobotDataset('your-user/so100-pick-place')
print(d.num_episodes, d.num_frames)"
# 2. 30000 frames, 20 passes, batch 8 -> 75000 steps
lerobot-train \
--policy.path=lerobot/smolvla_base \
--dataset.repo_id=your-user/so100-pick-place \
--batch_size=8 --steps=75000 --save_freq=7500 \
--dataset.eval_split=0.1 --eval_steps=1000 \
--output_dir=outputs/train/smolvla_pickplace \
--job_name=smolvla_pickplace --policy.device=cudaFor GR00T the entry point differs and the flags are a tyro CLI, not draccus. Copy the README example and replace max steps with your own arithmetic.
CUDA_VISIBLE_DEVICES=0 uv run python \
gr00t/experiment/launch_finetune.py \
--base-model-path nvidia/GR00T-N1.7-3B \
--dataset-path /data/so100_pick_place \
--embodiment-tag NEW_EMBODIMENT \
--modality-config-path examples/SO100/so100_config.py \
--num-gpus 1 \
--output-dir /tmp/run1 \
--max-steps 18750 \
--global-batch-size 32 \
--dataloader-num-workers 4The GR00T fine-tune entry point exposes no seed, so two identical commands do not produce identical weights. The repository notes 5 to 6 percent variance between runs from non-deterministic image augmentation. LeRobot does have a seed, default 1000. If you are comparing two run lengths on GR00T, that variance sits inside your measurement.
The training form does the same with the GPU rented for you: you pick model, dataset and hyperparameters, the backend rents a card on a spot market by required VRAM, runs the trainer and writes checkpoints to object storage. The numbers it sends are in the defaults table above, and max steps is one of the fields.
- Pick the combination on the training page, which links straight to the guide for that model and arm.
- Bring a dataset from the public directory, a Hugging Face repo id, or your own machine.
- Override max steps with the arithmetic above, especially for Pi0.5, GR00T N1.5 and SmolVLA where the default is close to a single pass.
- A100 80 GB or H100 tier: 3 to 6 hours at 1.20 to 2.00 USD per hour, about 4 to 12 USD. RTX 4090 tier: 2 to 5 hours at 0.30 to 0.60 USD per hour, about 1 to 3 USD.
- Serving the result provisions a pod with an idle watchdog that destroys itself after an idle period, so a forgotten endpoint does not keep billing.
Where it does not help: it will not tell you the right step count for your dataset, because that depends on frames and passes and nobody but you knows the frame count until the run starts. It cannot fix a dataset in the wrong format either, since a LeRobot v3.0 dataset crashes the GR00T loader and has to be converted down to v2.1 first, which is its own failure mode page. GR00T and Pi0.5 are cloud-only here; SmolVLA and ACT also run locally if you have a 24 GB card.
What a longer run does not fix
Run length is one of the smaller levers, and the one people reach for first because it is a single number in a form. These are the things no step count touches.
- Bad data. If the wrist camera pointed at the ceiling for twelve episodes, more passes learn that harder. See collecting high-quality VLA training data.
- Too few episodes. The floor is 30 for SmolVLA and 50 for the other four, and a floor is not a target. The SmolVLA documentation reports a 25-episode version of their pick-and-place dataset was not enough and performed badly.
- Inference latency. The control loop is 20 ms per action step for ACT, 152 ms for GR00T N1.7, 165 ms for GR00T N1.5, 245 ms for SmolVLA and 485 ms for Pi0.5. Training longer shortens none of them.
- Distance between the policy and the servos. Public-internet round trips on top of a 152 ms step turn a working policy into a hesitant one. Remote inference is viable for slow pick-and-place, not fast reactive motion.
- A task the demonstrations never showed. Generalisation comes from variation in the data, not from gradient steps over variation you never recorded.

One thing to take away: stop quoting step counts without the batch size next to them. Record steps, batch, effective batch and frame count for every run and the next dataset becomes arithmetic instead of a guess. The train your first policy walkthrough and the training documentation assume the defaults; this article is about the point where you stop assuming them. Still choosing a model? The GR00T N1.7 against SmolVLA comparison and the overview of vision-language-action models are the better starting points, and the model arena carries benchmark numbers for 85 of them.
Set the step count with the arithmetic in front of you
Every training guide names the GPU tier, dataset format and the defaults actually sent for that model and arm, so you can see what you are overriding before you launch.
Open the training guidesFrequently asked questions
How many steps should I train for on 50 episodes?▾
Compute it rather than copy it. Take the frame count from the dataset, target about 20 passes, divide by the effective batch size. For a 30000-frame dataset that is 300000 steps at batch 2, 75000 at batch 8 and 18750 at batch 32. Then evaluate three checkpoints on the arm.
Is more steps always better?▾
No, but stopping early is the more common mistake. The ACT authors recommend at least 5000 epochs on real-world data, or 3 to 4 times the length after the loss plateaus, because success and smoothness keep improving past that point. Against that, on a 30 to 50 episode dataset the extra passes increasingly memorise demonstrated positions, and cost scales linearly.
Why does my SmolVLA run only see one pass over my data?▾
Because the default batch size here is 2 and the gradient accumulation value of 8 is not applied, since lerobot 0.5.1 has no such flag. 20000 steps at an effective batch of 2 is 40000 samples, about 1.3 passes over a 30000-frame dataset. The upstream example uses batch 64 for the same 20000 steps, 32 times more data. Raise max steps, or the batch size if the card has memory.
Can I stop a run early and still use the checkpoint?▾
Yes, if a checkpoint was written, which is why save_freq should be about a tenth of the step count rather than the LeRobot default of 20000. One caveat: for Pi0.5 and SmolVLA the cosine schedule is sized to the step count you declared, so stopping halfway ends at a mid-slope learning rate. ACT has no scheduler, so an early checkpoint there is on equal footing with the last.
Do I need a validation split to decide when to stop?▾
It helps you see the curve but should not pick the checkpoint. LeRobot supports it through eval_steps together with dataset.eval_split, both off by default. The robomimic study found that selecting by lowest validation loss produced far worse policies than the best available checkpoint, in one case 2.7 percent success against 80.7 percent. Rollouts on the arm are the only measurement of the thing you want.
How much does doubling the run length cost?▾
Roughly double, since GPU time is close to linear in steps. On the A100 80 GB or H100 tier a run is 3 to 6 hours at 1.20 to 2.00 USD per hour, about 4 to 12 USD. On the RTX 4090 tier it is 2 to 5 hours at 0.30 to 0.60 USD per hour, about 1 to 3 USD, so there the arithmetic favours running long and picking a checkpoint.
Sources
- Zhao et al., Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware (ACT / ALOHA)
- tonyzhaozh/act: the original ACT implementation, training command and epoch advice
- SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics
- LeRobot documentation: fine-tuning SmolVLA on your own data
- lerobot/smolvla_base model card
- pi0.5: a Vision-Language-Action Model with Open-World Generalization
- openpi TrainConfig: num_train_steps and the pi05 fine-tuning configs
- LeRobot PI05Config: scheduler_warmup_steps, scheduler_decay_steps and optimizer defaults
- LeRobot TrainPipelineConfig: steps, batch_size, save_freq, eval_steps defaults
- LeRobot schedulers: CosineDecayWithWarmupSchedulerConfig auto-scaling
- LeRobot MetricsTracker: how the logged epoch count is computed
- NVIDIA Isaac-GR00T: fine-tuning commands, training tips and hardware guidance
- Isaac-GR00T FinetuneConfig: max_steps, save_steps, save_total_limit, warmup_ratio
- LeRobot: imitation learning on real-world robots, recording and training
- Mandlekar et al., What Matters in Learning from Offline Human Demonstrations for Robot Manipulation (robomimic)
Sources
- Zhao et al., Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware (ACT / ALOHA)
- tonyzhaozh/act: the original ACT implementation, training command and epoch advice
- SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics
- LeRobot documentation: fine-tuning SmolVLA on your own data
- lerobot/smolvla_base model card
- pi0.5: a Vision-Language-Action Model with Open-World Generalization
- openpi TrainConfig: num_train_steps and the pi05 fine-tuning configs
- LeRobot PI05Config: scheduler_warmup_steps, scheduler_decay_steps and optimizer defaults
- LeRobot TrainPipelineConfig: steps, batch_size, save_freq, eval_steps defaults
- LeRobot schedulers: CosineDecayWithWarmupSchedulerConfig auto-scaling
- LeRobot MetricsTracker: how the logged epoch count is computed
- NVIDIA Isaac-GR00T: fine-tuning commands, training tips and hardware guidance
- Isaac-GR00T FinetuneConfig: max_steps, save_steps, save_total_limit, warmup_ratio
- LeRobot: imitation learning on real-world robots, recording and training
- Mandlekar et al., What Matters in Learning from Offline Human Demonstrations for Robot Manipulation (robomimic)
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started