LeRobot · SO-101
Full Command Reference
Every command explained parameter by parameter — adapt each one for your hardware and task.
Pre-flight checklist
Do this every time before touching the robot. Skipping it causes confusing errors.
Step 1 — Serial port permissions
Grants read/write access to the arm motor buses. Required for both teleoperation and rollout.
sudo chmod 777 /dev/ttyACM* # open full permissions on all ACM serial devices ls -la /dev/ttyACM* # confirm the ports exist and permissions applied
Step 2 — Camera permissions
Fixes the "TimeoutError: Timed out waiting for frame" error when cameras won't open.
sudo chmod 666 /dev/video* # allow read/write on all video devices ls -la /dev/video* # confirm cameras are listed
Step 3 — Identify ports
Disconnect one arm, run the command, plug it back in — the printed port is that arm's address. Repeat for the other arm.
lerobot-find-port # detects a newly plugged USB serial device
Then identify which /dev/videoX corresponds to which physical camera:
lerobot-find-cameras opencv # lists all available OpenCV camera devices
Step 4 — Verify your port mapping
Before running any command, check these three things match your Step 3 output:
--robot.port= → FOLLOWER port (usually /dev/ttyACM0)
--teleop.port= → LEADER port (usually /dev/ttyACM1)
--robot.cameras → index_or_path values from lerobot-find-cameras
Teleoperation & Recording
Standard teleoperation
Use this to verify hardware is working and practice the task before committing to a recording session.
lerobot-teleoperate \
--robot.type=so101_follower \ # fixed — do not change
--robot.port=/dev/ttyACM1 \ # FOLLOWER port from lerobot-find-port
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video0, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video2, width: 640, height: 480, fps: 30}
}' \ # video devices from lerobot-find-cameras opencv
--robot.id=so101_follower_7V \ # any unique name for your follower — used for logging
--teleop.type=so101_leader \ # fixed — do not change
--teleop.port=/dev/ttyACM0 \ # LEADER port from lerobot-find-port
--teleop.id=so_101_leader_7V \ # any unique name for your leader — used for logging
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --robot.port | Your FOLLOWER port — the arm that physically moves (e.g. /dev/ttyACM0) |
| --teleop.port | Your LEADER port — the arm you hold and control (e.g. /dev/ttyACM1) |
| --robot.cameras | Replace index_or_path values with your /dev/videoX devices from lerobot-find-cameras |
| --robot.id / --teleop.id | Any unique label for your hardware — used in logs, can be anything |
| --display_data | true to open a live camera preview window, false to skip |
Record a dataset
Same as teleoperation but saves every episode to disk and pushes to Hugging Face Hub. Update the dataset flags for your specific task.
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \ # FOLLOWER port from lerobot-find-port
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video3, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video6, width: 640, height: 480, fps: 30}
}' \ # video devices from lerobot-find-cameras opencv
--robot.id=so101_follower_7V \ # unique name for your follower
--teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 \ # LEADER port from lerobot-find-port
--teleop.id=so_101_leader_7V \ # unique name for your leader
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \ # Hugging Face Hub destination
--dataset.single_task="Describe your task here" \ # plain English task description
--dataset.fps=30 \ # recording frame rate — match your camera fps
--dataset.num_episodes=50 \ # total number of episodes to collect
--dataset.episode_time_s=10 \ # how long each episode lasts (seconds)
--dataset.reset_time_s=2 \ # time between episodes to reset the scene
--dataset.root=data/my_dataset \ # local folder to store data before uploading
--dataset.streaming_encoding=true \ # encode video on the fly to save disk space
--dataset.vcodec=h264 \ # video codec — h264 is widely compatible
--dataset.encoder_threads=2 \ # CPU threads for encoding (2 is fine for Jetson)
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --robot.port | FOLLOWER port (the arm that moves) |
| --teleop.port | LEADER port (the arm you hold) |
| --robot.cameras | Replace index_or_path with your /dev/videoX devices from lerobot-find-cameras |
| --robot.id / --teleop.id | Any unique label — used for logging only |
| --dataset.repo_id | YOUR_HF_USERNAME/dataset-name — where it gets pushed on the Hub |
| --dataset.single_task | Plain English description of the task (e.g. "Pick up the red cube") |
| --dataset.num_episodes | 50 minimum for simple tasks, 100+ for harder ones — more diversity = better model |
| --dataset.episode_time_s | Set to just longer than your task takes — don't leave too much dead time |
| --dataset.reset_time_s | How long you have to reset the scene between episodes — increase if your reset is slow |
| --dataset.fps | Match your camera fps — 30 is standard |
| --dataset.root | Local save path — change if you want a different folder name |
| --dataset.vcodec | h264 is default and widely supported; h265 for better compression if supported |
| --dataset.encoder_threads | 2 works on Jetson; increase on a desktop for faster encoding |
Press → to save an episode and advance. Press ← to discard and re-record. Vary the object's starting position slightly between episodes — diversity prevents OOD freezing during inference.
Training
Basic ACT training
lerobot-train \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \ # your recorded dataset on the Hub
--policy.type=act \ # model architecture — act | diffusion | smolvla
--training.batch_size=32 \ # samples per gradient step — lower if you run out of memory
--training.num_epochs=100 \ # full passes through the dataset — increase if loss hasn't converged
--wandb.enable=trueParameter reference
| Flag | What to change it to |
|---|---|
| --dataset.repo_id | The HF Hub dataset you recorded — YOUR_HF_USERNAME/dataset-name |
| --policy.type | act for ACT, diffusion for Diffusion Policy, smolvla for SmolVLA |
| --training.batch_size | 32 is standard — drop to 8 or 16 if you get out-of-memory errors |
| --training.num_epochs | Start at 100, increase if validation loss is still falling at the end |
| --wandb.enable | true to log to W&B dashboard, false to train without logging |
Fine-tuning with LoRA & weight decay
Use LoRA for heavier models like SmolVLA to save GPU memory and reduce overfitting on small datasets.
lerobot-train \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \
--peft.method_type=LORA \ # parameter-efficient fine-tuning method
--peft.r=16 \ # LoRA rank — higher = more capacity, more memory (try 8, 16, 32)
--optimizer.weight_decay=1e-2 \ # L2 regularization strength to reduce overfitting
--optimizer.grad_clip_norm=1.0Parameter reference
| Flag | What to change it to |
|---|---|
| --peft.method_type | LORA is the supported option — enables low-rank adapter layers |
| --peft.r | LoRA rank: 8 = light, 16 = default, 32 = high capacity (more memory) |
| --optimizer.weight_decay | 1e-2 is a safe default — increase toward 1e-1 if the model overfits badly |
| --optimizer.grad_clip_norm | 1.0 is standard — lower to 0.5 if you see exploding gradients |
Data augmentation
Adds random color jitter and crops during training. Use this once the model has learned the basic task to improve robustness to lighting and position variation.
lerobot-train \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \
--policy.image_transforms.enable=true \ # turns on the augmentation pipeline
--policy.image_transforms.max_num_transforms=3Parameter reference
| Flag | What to change it to |
|---|---|
| --policy.image_transforms.enable | true to enable augmentation, false to train on raw images |
| --policy.image_transforms.max_num_transforms | 1–4 — more transforms = stronger augmentation = more robust but slower training |
Auto-evaluation during training
Runs the policy on the real robot periodically during training to track success rate without stopping the run.
lerobot-train \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \
--eval.n_episodes=5Parameter reference
| Flag | What to change it to |
|---|---|
| --eval.n_episodes | How many test episodes to run at each eval checkpoint — 5 is enough to get a signal without wasting too much time |
Rollout modes
All rollout modes share the same hardware flags (robot.port, robot.cameras, robot.id). The key differences are the strategy type, dataset flags, and policy flags.
Mode 1 — Base (evaluation only)
The policy drives the robot. Nothing is recorded. Use this just to watch the policy run.
Cannot use any --dataset.* flags in base mode.
lerobot-rollout \
--strategy.type=base \ # evaluation only — no recording
--robot.type=so101_follower \
--robot.port=/dev/ttyACM1 \ # FOLLOWER port from lerobot-find-port
--robot.id=so101_follower_7V \ # unique name for your follower
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video0, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video2, width: 640, height: 480, fps: 30}
}' \ # video devices from lerobot-find-cameras opencv
--policy.path=YOUR_HF_USER/MODEL_NAME \ # HF Hub model path or local checkpoint folder
--policy.revision=latest \ # which version to load — latest or a commit hash
--policy.temporal_ensemble_coeff=0.01 \ # blends predictions over time to smooth motion
--policy.n_action_steps=1 \ # actions executed per inference step
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --robot.port | FOLLOWER port from lerobot-find-port |
| --robot.cameras | Replace index_or_path with your /dev/videoX devices |
| --policy.path | HF Hub path (user/model-name) or local path to a pretrained_model folder |
| --policy.revision | latest for the newest version, or paste a specific commit hash to pin a version |
| --policy.temporal_ensemble_coeff | 0 = no blending (raw output), 0.01–0.1 = smooth motion, higher = more lag |
| --policy.n_action_steps | 1 = re-infer every step (most reactive), higher = execute a full chunk before re-inferring |
Mode 2 — Sentry (record evaluation)
Policy drives and always records every episode to a dataset. Use this to build an evaluation set or log policy behaviour over time.
lerobot-rollout \
--strategy.type=sentry \ # policy drives + always records
--dataset.repo_id=YOUR_HF_USER/EVAL_DATASET \ # where to push evaluation recordings
--dataset.num_episodes=10 \ # how many eval episodes to run
--dataset.episode_time_s=600 \ # max seconds per episode before auto-stopping
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \ # FOLLOWER port from lerobot-find-port
--robot.id=so101_follower_7V \
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video3, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video6, width: 640, height: 480, fps: 30}
}' \
--policy.path=YOUR_HF_USER/MODEL_NAME \ # policy to evaluate
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --dataset.repo_id | Where the recorded eval episodes get pushed — use a separate dataset from your training data |
| --dataset.num_episodes | How many evaluation runs to record |
| --dataset.episode_time_s | Max episode length in seconds — set long enough that the task can complete |
| --policy.path | HF Hub path or local checkpoint to the policy you want to evaluate |
Mode 3 — Highlight (triggered recording)
Policy drives but nothing is saved automatically. Press S whenever you see something worth keeping — it saves the last N seconds from a rolling buffer.
lerobot-rollout \
--strategy.type=highlight \ # policy drives, press S to save the last N seconds
--strategy.ring_buffer_seconds=10.0 \ # how many seconds of history to keep in the buffer
--dataset.repo_id=YOUR_HF_USER/HIGHLIGHTS \ # where saved clips get pushed
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \ # FOLLOWER port from lerobot-find-port
--robot.id=so101_follower_7V \
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video3, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video6, width: 640, height: 480, fps: 30}
}' \
--policy.path=YOUR_HF_USER/MODEL_NAME \
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --strategy.ring_buffer_seconds | Size of the rolling memory window in seconds — press S to save this many seconds of past data |
| --dataset.repo_id | Where the saved highlight clips get pushed on the Hub |
| --policy.path | The policy that will drive the robot autonomously |
Mode 4 — DAgger (human-in-the-loop)
Policy drives but you can take over at any time with the leader arm. The recorded data mixes autonomous and human-corrected segments — great for targeted improvement on failure cases.
lerobot-rollout \
--strategy.type=dagger \ # policy drives, human can intervene with leader arm
--dataset.repo_id=YOUR_HF_USER/DAGGER_DATA \ # where corrected episodes get saved
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \ # FOLLOWER port from lerobot-find-port
--robot.id=so101_follower_7V \
--teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 \ # LEADER port — needed so you can intervene
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video3, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video6, width: 640, height: 480, fps: 30}
}' \
--policy.path=YOUR_HF_USER/MODEL_NAME \
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --dataset.repo_id | Where DAgger correction episodes are saved — add this data to your training set and retrain |
| --teleop.port | LEADER port — required in DAgger so you can physically take over from the policy |
| --policy.path | The policy to run autonomously — typically your latest trained checkpoint |
Policy architectures
SmolVLA
A Vision-Language-Action model — understands natural language task descriptions alongside camera input. Requires extra dependencies.
# First install the smolvla extras
pip install 'lerobot[smolvla]'
lerobot-rollout \
--strategy.type=base \
--policy.path=YOUR_HF_USER/SMOLVLA_MODEL \ # your trained SmolVLA checkpoint
--dataset.episode_time_s=600 \ # max episode length — set generously for VLA models
--policy.device=cuda \ # cuda for GPU, cpu for CPU-only (much slower)
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --policy.path | Path to your SmolVLA checkpoint on the Hub or locally |
| --dataset.episode_time_s | VLA models are slower to infer — give them more time per episode |
| --policy.device | cuda for any NVIDIA GPU, cpu if no GPU available (expect very slow inference) |
Diffusion policy
Generates actions by iteratively denoising from random noise. More expressive than ACT for multi-modal tasks but slower to infer.
lerobot-rollout \
--strategy.type=base \
--policy.path=YOUR_HF_USER/DIFFUSION_MODEL \ # your trained diffusion checkpoint
--policy.num_inference_steps=5 \ # denoising steps — fewer = faster, less accurate
--policy.noise_scheduler_type=DDIM \ # DDIM is faster than DDPM at same quality
--policy.device=cuda \
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --policy.path | Path to your Diffusion Policy checkpoint |
| --policy.num_inference_steps | 5–10 is a good range — lower = faster inference, higher = more accurate actions |
| --policy.noise_scheduler_type | DDIM for faster sampling, DDPM for highest quality (slower) |
| --policy.device | cuda for GPU, cpu for CPU-only |
Overriding chunk size / action execution
Controls how many consecutive actions the model executes before re-inferring. Higher values are faster but less reactive to scene changes.
lerobot-rollout \
--strategy.type=base \
--policy.path=YOUR_HF_USER/MODEL_NAME \
--policy.n_action_steps=20 \ # execute 20 actions before running inference again
--display_data=trueParameter reference
| Flag | What to change it to |
|---|---|
| --policy.n_action_steps | 1 = re-infer every step (slowest, most reactive), 20–50 = execute a chunk (faster, less adaptive) |
Async inference
Splits the policy (GPU compute) and the robot controller (real-time I/O) into two separate processes communicating over a local socket. This removes inference latency from the control loop for higher throughput. Run each command in its own terminal.
Terminal 1 — Policy server
python -m lerobot.async_inference.policy_server \
--host=127.0.0.1 \ # address the server binds to — use 127.0.0.1 for local, 0.0.0.0 for network
--port=8080Parameter reference
| Flag | What to change it to |
|---|---|
| --host | 127.0.0.1 to stay local (safe default), 0.0.0.0 to accept connections from other machines on the network |
| --port | Any free port — must match --server_address in the robot client |
Terminal 2 — Robot client
python -m lerobot.async_inference.robot_client \
--server_address=127.0.0.1:8080 \ # must match host:port of the policy server
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \ # FOLLOWER port from lerobot-find-port
--robot.cameras='{
camera1: {type: opencv, index_or_path: /dev/video3, width: 640, height: 480, fps: 30},
camera2: {type: opencv, index_or_path: /dev/video6, width: 640, height: 480, fps: 30}
}' \ # video devices from lerobot-find-cameras opencv
--policy_type=act \ # architecture of the model being served
--pretrained_name_or_path=YOUR_HF_USER/MODEL_NAME \ # model to load on the server side
--actions_per_chunk=50Parameter reference
| Flag | What to change it to |
|---|---|
| --server_address | IP and port of the policy server — use 127.0.0.1:8080 if both are on the same machine |
| --robot.port | FOLLOWER arm port from lerobot-find-port |
| --robot.cameras | Replace index_or_path with your /dev/videoX devices |
| --policy_type | Architecture of your model — act | diffusion | smolvla |
| --pretrained_name_or_path | HF Hub path or local folder of the model to serve |
| --actions_per_chunk | Actions the client executes per server response — higher = lower network overhead, less reactive |
Utilities & troubleshooting
Replay a recorded episode
Plays back a specific episode from a dataset on the real robot — useful for verifying data quality before training.
lerobot-replay \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAME \ # the dataset to replay from
--dataset.episode_index=0Parameter reference
| Flag | What to change it to |
|---|---|
| --dataset.repo_id | The HF Hub dataset you want to replay — can be a training or eval dataset |
| --dataset.episode_index | 0-indexed episode number — check your dataset on the Hub to see how many episodes you have |
Visualize a dataset
Opens a browser-based viewer to browse all episodes in a dataset — inspect camera feeds and joint trajectories before training.
lerobot-dataset-viz \
--dataset.repo_id=YOUR_HF_USER/DATASET_NAMEParameter reference
| Flag | What to change it to |
|---|---|
| --dataset.repo_id | Any LeRobot-format dataset on the Hub — yours or someone else's |
Calibrate the robot
Run this after assembly or if the arm starts behaving unexpectedly. Saves calibration data to disk so all subsequent commands use it automatically.
lerobot-calibrate \
--robot.type=so101_follower \ # fixed — do not change
--robot.port=/dev/ttyACM0Parameter reference
| Flag | What to change it to |
|---|---|
| --robot.port | The port of the arm you want to calibrate — run separately for follower and leader if needed |
Fix permissions (quick reset)
If cameras or arms stop responding mid-session, run this before anything else.
sudo chmod 666 /dev/video* # reset camera permissions sudo chmod 666 /dev/ttyACM* # reset serial port permissions
If still unresponsive after this, unplug and replug all USB connections and re-run lerobot-find-port — port assignments can shift after a reconnect.
Want to understand how ACT works?
Read our deep dive on the architecture behind the model.