Part III: Running a trained policy · §31 of 43

31.1 Inputs checklist

You needWhere it comes fromCheck
A trained policylerobot-train (§24.3) or a Hub model id§31.2
The dataset it was trained on (locally)lerobot-record (§24.1)Needed only to look up the start pose and camera settings (§31.3)
The exact --robot.cameras string used when recordingYour recording command / shell history (history | grep lerobot-record)Copy it, don’t retype it
The same physical sceneTable, cameras, lighting, objectsCompare against a recorded frame (step 5 of §35)
Working robot setupsetup_openarm_lerobot.sh (§23.1)16/16 motors answer
GPURTX 5050 in the lerobot envpython -c "import torch; print(torch.cuda.is_available())" prints True
A second person (first runs)Hand on the motor power switch

31.2 Find and check the trained model

lerobot-train writes into the --output_dir you gave it, relative to the folder you ran it from. With the command of §24.3 run from ~/Documents/lerobot:

~/Documents/lerobot/outputs/train/act_openarm_pick_cube/
├── checkpoints/
│   ├── 020000/                     one folder per saved checkpoint (every --save_freq steps, default 20 000)
│   │   ├── pretrained_model/       ← what --policy.path points to
│   │   └── training_state/         optimiser state, only for resuming training
│   ├── 040000/
│   │   └── …
│   └── last -> 100000              link to the newest checkpoint
└── …                               training logs

and inside every pretrained_model/:

FileWhat it is
config.jsonThe policy’s settings: its type (act), the inputs it expects (input_features), what it outputs (output_features), chunk_size, n_action_steps, …
model.safetensorsThe trained weights (ACT: a few hundred MB)
train_config.jsonThe full training command, including which dataset was used
policy_preprocessor.json, policy_postprocessor.json (+ .safetensors files)The normalisation steps and the stats of the training data

Point --policy.path at pretrained_model, not at the checkpoint folder

Right: …/checkpoints/last/pretrained_model. Wrong: …/checkpoints/last. Use an absolute path ($HOME/Documents/lerobot/outputs/…): a relative one only works from the folder you trained in, and a path that doesn’t exist locally is treated as a Hugging Face Hub repo name, which fails with a confusing “repository not found” style error.

Check the folder and print what the policy expects:

conda activate lerobot
P=$HOME/Documents/lerobot/outputs/train/act_openarm_pick_cube/checkpoints/last/pretrained_model
ls -l "$P"
python - "$P" <<'EOF'
import json, sys, pathlib
p = pathlib.Path(sys.argv[1])
c = json.loads((p / "config.json").read_text())
print("policy type :", c.get("type"))
print("device      :", c.get("device"))
for k, v in c["input_features"].items():
    print("input  ", k, v["type"], v["shape"])
for k, v in c["output_features"].items():
    print("output ", k, v["type"], v["shape"])
for k in ("chunk_size", "n_action_steps", "n_obs_steps", "temporal_ensemble_coeff"):
    if k in c:
        print(f"{k:<24}", c[k])
t = json.loads((p / "train_config.json").read_text())
print("trained on  :", t["dataset"]["repo_id"])
EOF

What you should see for a bimanual ACT trained with one camera called top (illustrative):

policy type : act
device      : cuda
input   observation.state STATE [16]
input   observation.images.top VISUAL [3, 480, 640]
output  action ACTION [16]
chunk_size               100
n_action_steps           100
n_obs_steps              1
temporal_ensemble_coeff  None
trained on  : <hf_user>/openarm_pick_cube

How to read it:

  • observation.state [16] and action [16]: 8 values per arm, so it’s a bimanual policy and needs --robot.type=bi_openarm_follower. [8] would be a single-arm policy (§39).
  • Every observation.images.<name> line is a camera the robot must provide under exactly that <name>, at that resolution ([channels, height, width]).
  • trained on tells you which dataset to look at in §31.3.

A policy uploaded to the Hub (--policy.push_to_hub=true during training) is used with --policy.path=<hf_user>/act_openarm_pick_cube instead of a folder. It’s downloaded on first use.

31.3 Find out how the training data started

Two things come from the training dataset: the start pose and the camera settings. Datasets live in ~/.cache/huggingface/lerobot/<repo_id>/ (the repo_id printed above).

The fps and cameras the data was recorded with:

D=$HOME/.cache/huggingface/lerobot/<hf_user>/openarm_pick_cube
python - "$D" <<'EOF'
import json, sys
info = json.load(open(f"{sys.argv[1]}/meta/info.json"))
print("fps:", info["fps"], "| episodes:", info["total_episodes"], "| robot:", info.get("robot_type"))
for k, v in info["features"].items():
    if v["dtype"] in ("video", "image"):
        print("camera", k, v["shape"])
EOF

The --fps of the rollout must equal that fps, and every camera listed must be given in --robot.cameras with the same name and resolution.

The start pose. The policy has only ever seen the arms begin the task from wherever your recordings began. The safest place to start a rollout is therefore the average first frame of the training episodes. This prints it, per joint:

python - "$D" <<'EOF'
import glob, json, sys
import numpy as np, pandas as pd
root = sys.argv[1]
names = json.load(open(f"{root}/meta/info.json"))["features"]["observation.state"]["names"]
files = sorted(glob.glob(f"{root}/data/*/*.parquet"))
df = pd.concat(pd.read_parquet(f, columns=["episode_index", "frame_index", "observation.state"]) for f in files)
first = np.stack(df[df.frame_index == 0].sort_values("episode_index")["observation.state"].to_numpy())
print(f"{len(first)} episodes. First-frame joint angles (degrees):")
print(f"{'joint':<22}{'mean':>8}{'min':>8}{'max':>8}")
for i, n in enumerate(names):
    print(f"{n:<22}{first[:, i].mean():8.1f}{first[:, i].min():8.1f}{first[:, i].max():8.1f}")
EOF

If your recordings started with the arms hanging and the grippers closed, every mean is within a few degrees of 0. A wide min–max spread on a joint means the episodes started in different places. That’s fine, it makes the policy more tolerant, but stay inside that range when you position the arms.

Write the start pose down. It’s the target of step 6 in §35.