Skip to content

How to Train an ACT Policy on the SO-101 Arm with LeRobot: From Teleoperation Data to Rollout

Set up SO-101 leader and follower arms with Hugging Face LeRobot, record a teleoperation dataset, then train and run an ACT policy, with commands from the docs.

Contents

Key takeaways

  1. The SO-101 follower uses six STS3215 motors with 1/345 gearing; the leader mixes 1/191, 1/345 and 1/147 motors so it holds its own weight yet moves easily by hand.
  2. Motor ID setup (lerobot-setup-motors) and calibration (lerobot-calibrate) are one-time steps per arm, and the id you choose must be reused in every later command because it keys the calibration file.
  3. The docs recommend a pick-and-place starter task with at least 50 episodes, 10 per object location, recorded with fixed cameras.
  4. LeRobot’s ACT defaults are chunk_size 100, n_action_steps 100 and a ResNet18 backbone; enabling temporal ensembling requires n_action_steps = 1.
  5. lerobot-train with --policy.type=act adapts the model to the joints and cameras stored in the dataset, and lerobot-rollout runs the trained policy on the robot.

The SO-101 is the low-cost six-axis arm that the Hugging Face LeRobot documentation uses as its flagship example. You move a leader arm by hand, the follower arm mirrors it, and every motion is recorded as a demonstration. This guide walks an engineer who is new to LeRobot through the whole loop, in order: motor setup, dataset recording, ACT training and running the policy on the real robot. Each step ends with what to check before moving on, so you can tell whether a problem comes from hardware, data or training.

If you want the bigger picture first, our overview of Physical AI and world models explains where imitation learning and action-generation models sit in the field.

Hardware and software you need before starting

According to the SO-101 assembly guide, all six follower joints use Feetech STS3215 motors with 1/345 gearing. The leader arm deliberately mixes gear ratios so that it can hold its own weight while still moving with little force:

Leader jointMotorGear ratio
Base / Shoulder Pan11/191
Shoulder Lift21/345
Elbow Flex31/191
Wrist Flex41/147
Wrist Roll51/147
Gripper61/147

The bill of materials and the 3D-printable parts are listed in TheRobotStudio’s SO-ARM100 repository README. On the software side, the installation guide sets these requirements:

ItemRequirement
Python3.12 (conda or uv environment)
PyTorch2.10 or newer supported
Video decodingTorchCodec + ffmpeg (falls back to pyav on macOS Intel, Linux ARM and other unsupported platforms)
Motor SDKthe feetech extra
Training hardwareNVIDIA GPU (cuda) or Apple silicon (mps)

Step 1: Install LeRobot with the Feetech SDK

Recording, replay and calibration need the core_scripts extra, training needs training, and the SO-101 servos need feetech. If one machine will both record and train, install all three:

conda create -y -n lerobot python=3.12
conda activate lerobot
conda install ffmpeg -c conda-forge

git clone https://github.com/huggingface/lerobot.git
cd lerobot
pip install -e ".[core_scripts,training,feetech]"

The installation guide adds two platform notes. Under WSL, also install evdev. If the build fails, install cmake, build-essential and the ffmpeg development libraries before retrying.

Step 2: Assign motor IDs and calibrate both arms

Brand-new servos usually ship with ID 1, so a controller cannot tell them apart on a shared bus. Each motor therefore gets a unique ID and a common baudrate written to its EEPROM. You only do this once per motor.

  1. Find the ports. Connect a controller board over USB and power, run lerobot-find-port, and unplug the cable when asked. The script prints the port for that arm, for example /dev/ttyACM0. On Linux you may need sudo chmod 666 /dev/ttyACM0 to grant access.
  2. Set up the follower motors. The command below prompts you to connect one motor at a time, starting with the gripper. Only that motor may be attached to the board when you press Enter.
  3. Set up the leader motors. Repeat the process with --teleop.type=so101_leader.
  4. Calibrate. Place every joint in the middle of its range, press Enter, then sweep each joint through its full range. Do this once for the follower and once for the leader.
lerobot-setup-motors \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0

lerobot-setup-motors \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1

lerobot-calibrate \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.id=my_follower_arm

lerobot-calibrate \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1 \
    --teleop.id=my_leader_arm

The real-robot imitation learning guide explains that id is the key under which the calibration file is stored. Keep my_follower_arm and my_leader_arm identical in every later command. If you use a Waveshare controller board, also check that both jumpers sit on the B (USB) channel.

Step 3: Verify the setup with teleoperation

Before recording, confirm that the follower tracks the leader pose for pose. Adding a camera and --display_data=true opens a rerun view with the video feed and joint values side by side:

lerobot-teleoperate \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.id=my_follower_arm \
    --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1 \
    --teleop.id=my_leader_arm \
    --display_data=true

If any device is missing a calibration, this command starts the calibration routine first. When a single joint moves in the wrong direction or jumps at the end of its travel, recalibrating that arm is the quickest thing to rule out.

Step 4: Record the dataset

Recordings are saved in the LeRobotDataset format under ~/.cache/huggingface/lerobot/{repo-id}, and by default they are pushed to the Hugging Face Hub when recording ends. Log in with a write-access token first:

hf auth login --token ${HUGGINGFACE_TOKEN} --add-to-git-credential
HF_USER=$(NO_COLOR=1 hf auth whoami | awk -F': *' 'NR==1 {print $2}')

lerobot-record \
    --robot.type=so101_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.id=my_follower_arm \
    --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1 \
    --teleop.id=my_leader_arm \
    --display_data=true \
    --dataset.repo_id=${HF_USER}/so101_pick_place \
    --dataset.num_episodes=50 \
    --dataset.single_task="Pick up the cube and place it in the bin"

The recording flow is driven from the keyboard. The letter keys n, r and q also work on Wayland and headless SSH sessions, as long as the terminal has focus:

KeyAction
→ or nEnd the current episode (or reset period) early and move on
← or rDiscard the current episode and record it again
ESC or qStop the session, encode the videos and upload

--dataset.episode_time_s sets the episode length and --dataset.reset_time_s the reset window; both default to 60 seconds. For a first task the docs suggest grasping an object at several locations and dropping it into a bin, with at least 50 episodes, 10 per location. Keep the cameras fixed, grasp the same way each time, and check that a person could complete the task from the camera images alone. If a session is interrupted, rerun the same command with --resume=true and --dataset.root, and set num_episodes to the number of additional episodes, not the target total.

Step 5: Train the ACT policy

ACT, short for Action Chunking with Transformers, was introduced by Zhao et al. in 2023. Instead of predicting one action per step, it predicts a whole sequence (a chunk) at once, which reduces the compounding of small errors over time. The paper’s abstract reports 80–90% success on six real-world tasks learned from only 10 minutes of demonstrations on low-cost hardware. LeRobot also trains Diffusion Policy, SmolVLA and Pi0-family policies through the same command by changing policy.type, so validating your data pipeline with ACT first makes later model comparisons straightforward.

These are the defaults in LeRobot’s ACT configuration that most directly shape behavior:

ParameterDefaultMeaning
chunk_size100Number of action steps predicted at once
n_action_steps100Steps of each chunk actually executed (must not exceed chunk_size)
vision_backboneresnet18Initialized from ImageNet weights
use_vaeTrueModels variation across demonstrations with a latent variable (latent_dim 32)
temporal_ensemble_coeffNoneWhen enabled, inference runs every step and chunks are averaged; n_action_steps must then be 1
optimizer_lr1e-5The backbone learning rate is also 1e-5

You only need to pass the dataset and the policy type. The model’s inputs and outputs adapt to the joints and cameras stored in the dataset:

lerobot-train \
  --dataset.repo_id=${HF_USER}/so101_pick_place \
  --policy.type=act \
  --output_dir=outputs/train/act_so101_pick_place \
  --job_name=act_so101_pick_place \
  --policy.device=cuda \
  --wandb.enable=true \
  --policy.repo_id=${HF_USER}/act_so101_pick_place

On Apple silicon, switch to --policy.device=mps. The docs say training takes several hours, and checkpoints accumulate in outputs/train/act_so101_pick_place/checkpoints. To continue an interrupted run, pass --config_path=.../checkpoints/last/pretrained_model/train_config.json --resume=true. Without a capable local GPU, add --job.target=a10g-small to run on a Hugging Face Jobs cloud GPU instead.

Step 6: Run the trained policy on the robot

Deployment uses lerobot-rollout. The base strategy runs the policy without recording, while sentry keeps recording rollouts into an evaluation dataset and uploads them. Camera names and settings must match the training data:

lerobot-rollout \
  --strategy.type=base \
  --policy.path=${HF_USER}/act_so101_pick_place \
  --robot.type=so101_follower \
  --robot.port=/dev/ttyACM0 \
  --robot.id=my_follower_arm \
  --robot.cameras="{front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
  --task="Pick up the cube and place it in the bin" \
  --duration=60

If the policy will eventually run continuously on an edge board, memory requirements change sharply once you move from ACT to a larger VLA policy. Our edge GPU VRAM sizing guide for on-device VLA and the VLA VRAM calculator help estimate that gap before you buy hardware.

Four data checks when the success rate is low

When the policy hesitates near the object or heads to the wrong place, look at the dataset before touching hyperparameters:

  1. Camera framing. Confirm the camera position and angle at rollout match the recording setup. The docs list fixed cameras as the first condition for good data.
  2. Amount of variation. If you changed grasp locations, lighting and objects all at once, cut back to one varying factor and retrain. The docs advise adding variation gradually once grasping is reliable.
  3. Episode quality. Replay a recorded episode with lerobot-replay --dataset.episode=0. This separates shaky demonstrations from poor robot repeatability.
  4. Motion smoothness. If motion stutters at chunk boundaries, set n_action_steps below chunk_size, or try temporal ensembling (temporal_ensemble_coeff with n_action_steps=1). Ensembling runs inference every step, so check that your inference hardware has the compute headroom.

Common mistakes

  • Daisy-chaining all new motors before setting IDs. Factory motors usually share ID 1, so only one motor may be connected to the board at a time.
  • Using a different robot.id when recording or running than the one used for calibration, which loads the wrong joint offsets.
  • Changing object positions, lighting and camera angles all at once in the first dataset, so 50 episodes are not enough for the policy to converge.
  • Setting --dataset.num_episodes to the final total when resuming a recording; on resume it must be the number of additional episodes.

Pre-flight checklist

  • A Python 3.12 environment is active and ffmpeg is installed where TorchCodec is supported.
  • The leader and follower ports were identified with lerobot-find-port and written down.
  • Both arms are calibrated and every command uses the same ids.
  • Cameras are fixed, and a person could do the task while looking only at the camera feeds.
  • You are logged in with a Hugging Face write token and dataset.repo_id is under your account.
  • policy.device matches your training hardware (cuda for NVIDIA GPUs, mps for Apple silicon).

Frequently asked questions

Can I train ACT on a laptop without a GPU?
The docs point to two options when local compute is weak: the ACT training notebook on Google Colab, or Hugging Face Jobs, where --job.target selects a cloud GPU. On Apple silicon you can train with policy.device=mps.
Does the same workflow apply to the SO-100?
Yes for recording, training and rollout: LeRobot exposes separate robot types such as so100_follower with the same command structure. Assembly and motor configuration differ by robot, so follow that robot’s own documentation page.
What changes if I want to train a VLA policy instead of ACT?
Keep lerobot-train but switch policy.type to SmolVLA or a Pi0-family policy and install the matching extra, such as lerobot[smolvla]. Larger models need considerably more GPU memory for both training and inference, so size your hardware first.

Sources & verification

  1. Hugging Face LeRobot Docs — SO-101
  2. Hugging Face LeRobot Docs — Imitation Learning on Real-World Robots
  3. Hugging Face LeRobot Docs — Installation
  4. LeRobot GitHub — configuration_act.py (ACT defaults)
  5. Zhao et al., Learning Fine-Grained Bimanual Manipulation with Low-Cost Hardware (arXiv:2304.13705)

Last verified: