LeRobot documentation
Déploiement de la politique (lerobot-rollout)
Déploiement de la politique (lerobot-rollout)
lerobot-rollout est l’unique CLI pour déployer des politiques entraînées sur des robots réels. Il prend en charge plusieurs stratégies d’exécution et backends d’inférence, de l’évaluation rapide à l’enregistrement continu et à la collecte de données avec intervention humaine.
Démarrage rapide
Aucune dépendance supplémentaire n'est nécessaire au-delà des extras de votre robot et de votre politique.
lerobot-rollout \
--strategy.type=base \
--policy.path=lerobot/act_koch_real \
--robot.type=koch_follower \
--robot.port=/dev/ttyACM0 \
--task="pick up cube" \
--duration=30Cela exécute la politique pendant 30 secondes sans enregistrement.
Choisir un appareil
Passez --device=cpu ou --device=cuda pour remplacer le device du checkpoint sur un hôte où ce device est disponible.
Le device résolu est appliqué à la configuration de la politique avant de charger la politique et de construire ses processeurs.
Par exemple, ajoutez --device=cpu à la commande ci-dessus pour exécuter un checkpoint entraîné sur CUDA sur un hôte CPU uniquement, si la politique prend en charge l'exécution CPU.
Vérifiez que l'inférence CPU peut répondre à votre taux de contrôle requis avant de déployer sur un robot.
Lorsque --device est omis, le rollout utilise le dispositif de la configuration de la politique s’il est défini, sinon il sélectionne automatiquement un dispositif disponible.
Le même comportement s’applique à RolloutConfig(device=...) avec build_rollout_context.
Stratégies
Sélectionnez une stratégie avec --strategy.type=<name>. Chaque stratégie définit une boucle de contrôle différente avec ses propres sémantiques d’enregistrement et d’interaction.
Base ( --strategy.type=base )
Exécution autonome de la politique sans enregistrement de données. Utilisez ce mode pour une évaluation rapide, des démonstrations ou lorsque vous avez uniquement besoin d’observer le robot.
lerobot-rollout \
--strategy.type=base \
--policy.path=${HF_USER}/my_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
--task="Put lego brick into the box" \
--duration=60| Drapeau | Description | Par défaut |
|---|---|---|
--duration | Temps d'exécution en secondes (0 = infini) | 0 |
--task | Description de la tâche transmise à la politique | — |
--display_data | Diffuser les observations/actions vers Rerun pour la visualisation | false |
Sentry ( --strategy.type=sentry )
Enregistrement autonome continu avec téléversement périodique sur le Hugging Face Hub. Les limites des épisodes sont calculées automatiquement à partir de la résolution de la caméra et du FPS, de sorte que chaque épisode enregistré génère un fichier vidéo complet, ce qui maintient les téléversements efficaces.
L'état de la politique (état caché, file RTC) persiste au-delà des limites des épisodes : le robot n'est pas réinitialisé entre les épisodes.
lerobot-rollout \
--strategy.type=sentry \
--strategy.upload_every_n_episodes=5 \
--policy.path=${HF_USER}/my_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
--dataset.repo_id=${HF_USER}/rollout_eval_data \
--dataset.single_task="Put lego brick into the box" \
--duration=3600| Drapeau | Description | Par défaut |
|---|---|---|
--strategy.upload_every_n_episodes | Pousser vers le Hub tous les N épisodes | 5 |
--strategy.target_video_file_size_mb | Taille de fichier vidéo cible pour la rotation des épisodes | auto |
--dataset.repo_id | Obligatoire. Dépôt Hub du jeu de données enregistré | — |
--dataset.push_to_hub | Indique s'il faut pousser vers le Hub lors de la démolition | true |
Mise en surbrillance ( --strategy.type=highlight )
Déploiement autonome avec enregistrement à la demande via un tampon circulaire à mémoire bornée. Le tampon capture les N dernières secondes de télémétrie. Appuyez sur la touche d’enregistrement pour vider le tampon et démarrer l’enregistrement en direct ; appuyez à nouveau pour enregistrer l’épisode. Avec un backend asynchrone, chaque bascule met l’inférence en pause, invalide le mouvement en file d’attente ou interpolé et demande une pause locale prise en charge avant le vidage bloquant ou l’enregistrement. Une exécution saine reprend à partir d’une nouvelle capture par la suite.
lerobot-rollout \
--strategy.type=highlight \
--strategy.ring_buffer_seconds=30 \
--strategy.save_key=s \
--strategy.push_key=h \
--policy.path=${HF_USER}/my_policy \
--robot.type=koch_follower \
--robot.port=/dev/ttyACM0 \
--dataset.repo_id=${HF_USER}/rollout_highlight_data \
--dataset.single_task="Pick up the red cube"Contrôles au clavier :
| Clé | Action |
|---|---|
s (configurable) | Démarrer l'enregistrement (vide le tampon) / arrêter et enregistrer l'épisode |
h (configurable) | Pousser le jeu de données vers le Hub |
ESC | Arrêter la session |
| Drapeau | Description |
|---|---|
--strategy.ring_buffer_seconds | Durée de la télémétrie mise en mémoire tampon (par défaut : 10) |
--strategy.ring_buffer_max_memory_mb | Limite de mémoire pour le tampon circulaire (par défaut : 1024) |
--strategy.save_key | Touche pour activer/désactiver l'enregistrement (par défaut : s) |
--strategy.push_key | Touche pour pousser vers le Hub (par défaut : h) |
DAgger ( --strategy.type=dagger )
Collecte de données avec intervention humaine. Alterne entre l'exécution autonome de la politique et l'intervention humaine via un téléopérateur. Les images d'intervention sont étiquetées avec intervention=True. Nécessite un téléopérateur (--teleop.type).
Consultez le guide Collecte de données avec intervention humaine pour un tutoriel détaillé.
Mode corrections uniquement (par défaut) : Seules les fenêtres de correction humaine sont enregistrées. Chaque correction devient un épisode.
lerobot-rollout \
--strategy.type=dagger \
--strategy.num_episodes=20 \
--policy.path=outputs/pretrain/checkpoints/last/pretrained_model \
--robot.type=bi_openarm_follower \
--teleop.type=bi_openarm_mini \
--dataset.repo_id=${HF_USER}/rollout_hil_data \
--dataset.single_task="Fold the T-shirt"Mode d'enregistrement continu (--strategy.record_autonomous=true) : Les images autonomes et de correction sont enregistrées avec une rotation des épisodes basée sur le temps (identique à Sentry).
lerobot-rollout \
--strategy.type=dagger \
--strategy.record_autonomous=true \
--strategy.num_episodes=50 \
--policy.path=${HF_USER}/my_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--teleop.type=so101_leader \
--teleop.port=/dev/ttyACM1 \
--dataset.repo_id=${HF_USER}/rollout_dagger_data \
--dataset.single_task="Grasp the block"Contrôles au clavier (périphérique d’entrée par défaut) :
| Clé | Action |
|---|---|
Space | Mettre en pause / reprendre l’exécution de la politique |
Tab | Démarrer / arrêter la correction humaine |
Enter | Pousser le jeu de données vers le Hub (mode corrections uniquement) |
ESC | Arrêter la session |
La saisie via pédale est également prise en charge via --strategy.input_device=pedal. Configurez les codes de pédale avec les drapeaux --strategy.pedal.*.
Lorsque le robot prend en charge le maintien de la position de déroulement, une pause actualise la dernière cible de position réellement appliquée par le pilote, y compris son rognage et la cible de la pince. Avant la première commande, une pose mesurée et validée fournit la cible. Cela préserve l'erreur de position commandée au lieu de modifier la cible pour correspondre à la pose mesurée ; le robot peut ainsi terminer sa stabilisation vers cette cible pendant que l'avancement de la trajectoire de la politique est arrêté. Validez le comportement du bras chargé et de la pince sur le robot réel ; ce maintien logiciel ne garantit pas le maintien de la prise ni l'immobilité physique.
| Drapeau | Description |
|---|---|
--strategy.num_episodes | Nombre d'épisodes de correction à enregistrer (par défaut : --dataset.num_episodes) |
--strategy.record_autonomous | Enregistrer également les images autonomes (par défaut : false) |
--strategy.upload_every_n_episodes | Pousser vers le Hub toutes les N épisodes (par défaut : 5) |
--strategy.input_device | Périphérique d'entrée : keyboard ou pedal (par défaut : keyboard) |
--strategy.smooth_handover | Prendre le contrôle en douceur au moment de la pause / du début de la correction (par défaut : true). À désactiver pour les téléopérations de type « clutch » qui réinitialisent la référence sur la pose actuelle du robot lors de l’activation |
--teleop.type | Obligatoire. Type de téléopérateur |
Épisodique ( --strategy.type=episodic )
Enregistrement orienté par épisode qui reproduit le comportement de lerobot-record. La politique pilote le robot pour chaque épisode ; un téléopérateur facultatif peut piloter le robot pendant la phase de réinitialisation entre les épisodes.
lerobot-rollout \
--strategy.type=episodic \
--policy.path=${HF_USER}/my_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyACM1 \
--dataset.repo_id=${HF_USER}/rollout_eval_data \
--dataset.num_episodes=20 \
--dataset.episode_time_s=30 \
--dataset.reset_time_s=10 \
--dataset.single_task="Pick up the red cube"Teleop est facultatif — s'il est omis, le robot maintient sa position pendant la phase de réinitialisation.
Contrôles au clavier :
| Clé | Action |
|---|---|
→ (droite) | Mettre fin à l'épisode en cours |
← (gauche) | Abandonner l'épisode et le réenregistrer |
ESC | Arrêter la session d'enregistrement |
| Drapeau | Description |
|---|---|
--dataset.num_episodes | Nombre d'épisodes à enregistrer |
--dataset.episode_time_s | Durée de chaque épisode d'enregistrement, en secondes. Par défaut, la valeur de --duration est utilisée si celle-ci est définie et que la présente ne l'est pas. |
--dataset.reset_time_s | Durée de la phase de réinitialisation entre les épisodes, en secondes |
--teleop.type | Facultatif. Téléopérateur pour piloter le robot lors des réinitialisations |
--strategy.reset_to_initial_position | Indique si le robot doit être réinitialisé à sa position initiale entre les épisodes |
--strategy.smooth_leader_to_follower_handover | Indique s'il faut activer ou désactiver le comportement de basculement fluide du leader vers le suiveur. |
--strategy.smooth_handover | Transfère le contrôle au téléop de manière fluide au début du réinitialisation (valeur par défaut : true). À désactiver pour les téléops de type « clutch » qui se ré-étalonnent sur la pose actuelle du robot lors de l’activation |
Apportez votre propre stratégie
--strategy.type accepte toute stratégie enregistrée dans l’environnement, pas seulement les stratégies intégrées ci-dessus. Une stratégie est constituée de deux classes dans un package installable via pip : une configuration enregistrée sur RolloutStrategyConfig, et une sous-classe de RolloutStrategy nommée d’après elle sans le suffixe Config — la même convention SomethingConfig/Something que pour le matériel personnalisé, résolue à partir du module ou du package de la configuration.
from dataclasses import dataclass
from typing import ClassVar
from lerobot.rollout import RolloutContext, RolloutStrategy, RolloutStrategyConfig
@RolloutStrategyConfig.register_subclass("patrol")
@dataclass
class PatrolStrategyConfig(RolloutStrategyConfig):
dataset_mode: ClassVar[str] = "required" # the engine creates the dataset for you
legs: int = 2 # becomes --strategy.legs
class PatrolStrategy(RolloutStrategy):
def run(self, ctx: RolloutContext) -> None:
... # the paced control loop: see the contract on RolloutStrategy.run
def teardown(self, ctx: RolloutContext) -> None:
ctx.data.dataset.finalize()
self._teardown_hardware(ctx.hardware, ctx.runtime.cfg.return_to_initial_position)La configuration déclare ce que le moteur organise au nom de la stratégie, de sorte qu’aucun élément de LeRobot n’a besoin de connaître son type :
| Déclaration | Effet |
|---|---|
dataset_mode | "none" (par défaut) rejette les drapeaux --dataset.*, "optional" les enregistre lorsqu’ils sont fournis, "required" rend --dataset.repo_id obligatoire |
requires_teleop | Rend --teleop.type obligatoire |
supports_interactive | Permet --interactive=true ; run() doit alors respecter le contrat de redémarrage documenté sur RolloutStrategy |
supports_sim_robot | Autorise --robot.type=sim ; run() doit alors démarrer un nouvel épisode à chaque fois qu'un épisode se termine (voir lerobot.robots.sim_robot) |
requires_streaming_encoding() | Retourne True lorsque les images sont écrites depuis l'intérieur de la boucle de contrôle : --dataset.streaming_encoding est forcé |
extra_dataset_features() | Les colonnes de jeu de données appartenant à la stratégie (par exemple, l'intervention de DAgger) ; chaque frame enregistrée doit les contenir |
Nommez la distribution lerobot_strategy_<name> et lerobot-rollout importe le paquet avant d’analyser la CLI — seul son __init__.py s’exécute, il faut donc importer la configuration à cet endroit. Un paquet portant tout autre nom est chargé, sous-modules inclus, avec --strategy.discover_packages_path=<package>. Les champs de la dataclass de la stratégie deviennent des drapeaux --strategy.* :
lerobot-rollout \
--strategy.type=patrol \
--strategy.legs=3 \
--policy.path=${HF_USER}/my_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--dataset.repo_id=${HF_USER}/rollout_patrol \
--dataset.single_task="patrol the aisle"SentryStrategy (enregistrement, sûr en mode interactif) et BaseStrategy (sans enregistrement) sont les implémentations de référence à copier ; send_next_action, safe_push_to_hub et estimate_max_episode_seconds de lerobot.rollout.strategies sont les utilitaires sur lesquels elles s’appuient.
Pour les backends asynchrones, le contrat de dispatch est obligatoire. Publiez chaque observation capturée avec engine.notify_observation(obs_processed), appelez send_next_action(...) à chaque tick moteur autonome, y compris les ticks d’interpolation et de maintien, puis appelez engine.pump_query(obs_processed). Appeler uniquement engine.get_action() contourne la poignée de main d’autorisation et de maintien : une requête texte planifiée peut attendre indéfiniment, et une panne terminale peut ne jamais signaler l’arrêt.
Les stratégies tierces peuvent appeler self._tick(timer, new_cycle=interpolator.needs_new_action()) à la place de timer.tick(...) pour inclure un rapport périodique de l’état de santé après _init_engine(ctx).
send_next_action est construit sur lerobot.inference.dispatch_tick et ses composants (next_interpolated_action, commit_action, dispatch_permitted), qui encodent cet ordre une seule fois : dispatch_allowed() avant et après le retrait d'une action, puis à nouveau avant l'envoi, l'invalidation de l'interpolation ainsi qu'un maintien local pris en charge et acknowledge_hold() en cas de refus, et record_dispatch() après l'envoi. Un maintien qui échoue n'est jamais confirmé. Utilisez ces utilitaires plutôt que de réimplémenter la séquence ; send_next_action n'ajoute que le cadre du jeu de données, le processeur d'action côté robot et la comptabilité de la cadence. Dans les contextes distants, ctx.policy.policy, preprocessor et postprocessor sont définis sur None ; utilisez ctx.policy.inference et les pipelines côté robot dans ctx.processors.
Envelopper le travail de capture bloquant connu en cours d’exécution dans self._pause_for_recording(ctx), y compris le vidage des tampons circulaires et la finalisation des épisodes. Cela invalide le mouvement asynchrone avant l’arrêt de la capture et ne reprend que si l’exécution est toujours saine et active. Les stratégies avec des phases contrôlées par l’opérateur doivent transmettre leur autorisation de mouvement actuelle en tant que resume_allowed. La prochaine action doit provenir d’une observation fraîche ; le travail sauvegardé ne doit pas réveiller une exécution en pause, arrêtée ou en défaut.
Visualisation des prédictions de la politique
Avec --display_data, le visualiseur affiche également ce que la politique prédit en plus des actions : les images de caméra imaginées par un modèle du monde, une sous-tâche prédite, des boîtes englobantes dessinées sur une caméra. Chaque politique décide de ce qu’elle prédit et l’active via sa propre configuration, par exemple --policy.return_predicted_video=true pour LingBot-VA. Une prédiction est nommée d’après ce qu’elle prédit :
Rerun affiche une observation.images.top prédite sous le nom prediction.images.top, une sous-tâche prédite sous le nom prediction.subtask, et les boîtes sous leur caméra (<camera>/prediction) ; Foxglove les publie
sur les sujets /prediction/... et les annotations d’image <camera topic>/prediction. Avec RTC, la
prédiction est actualisée une fois par bloc. Consultez Bring your own policy pour renvoyer
une prédiction depuis une politique.
Backends d'inférence
Sélectionnez un backend avec --inference.type=<name>. L’inférence locale synchrone et l’inférence RTC utilisent la politique configurée ci-dessous. Le backend distant obtient son contrat de politique depuis un serveur et ne charge pas les poids sur l’ordinateur du robot.
Synchronisation (par défaut)
Un appel de politique par tic de contrôle. La boucle principale est bloquée jusqu'à ce que l'action soit calculée.
Fonctionne avec toutes les politiques. Aucun drapeau supplémentaire n'est nécessaire.
Découpage en temps réel ( --inference.type=rtc )
Un thread d’arrière-plan produit des segments d’actions de manière asynchrone. La boucle de contrôle principale interroge l’action suivante prête à l’emploi pendant que la politique calcule le segment suivant en parallèle.
Utilisez RTC avec des modèles VLA compatibles (Pi0, Pi0.5, SmolVLA) pour superposer l’inférence et le mouvement. L’exécution continue nécessite toujours un temps de lecture d’actions suffisant pour couvrir la latence d’inférence et ses variations.
lerobot-rollout \
--strategy.type=base \
--inference.type=rtc \
--inference.rtc.execution_horizon=10 \
--inference.rtc.max_guidance_weight=10.0 \
--policy.path=${HF_USER}/pi0_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
--task="Pick up the cube" \
--duration=60 \
--device=cuda| Drapeau | Description |
|---|---|
--inference.rtc.execution_horizon | Étapes pour fusionner avec le bloc précédent (valeur par défaut : varie selon la politique) |
--inference.rtc.mode | guided (par défaut) ou préfixe entraîné trained pour les points de contrôle Pi05 compatibles |
--inference.rtc.max_guidance_weight | Force d'application de la cohérence (par défaut : varie selon la politique) |
--inference.rtc.prefix_attention_schedule | Plan de mélange : LINEAR, EXP, ONES, ZEROS |
--inference.queue_threshold | Seuil de contre-pression ; RTC entraîné nécessite au moins son délai maximal |
--inference.action_starvation_grace_s | Attendre un mouvement frais après une période de famine sur un robot pris en charge (valeur par défaut 1.0 ; 0 déclenche une erreur immédiatement) |
Consultez le guide Real-Time Chunking pour plus de détails sur le réglage des paramètres RTC.
Le RTC local s’exécute dans un thread en arrière-plan du processus de rollout, sans transport. Un serveur et un client sur le même ordinateur utilisent à la place --inference.type=remote. Consultez le guide distant pour l’alignement et le mélange des chunks, ainsi que le comportement d’attente partagé par les backends asynchrones.
L’attente nécessite un robot pris en charge. Le RTC local ordinaire reste disponible sans cette capacité, mais les pauses planifiées pour les changements de texte/instruction et la famine récupérable en ont besoin. Les suiveurs à contrôle de position SO, OMX, Koch, HopeJr et Rebot le prennent en charge ; OpenArm, Reachy2 et les enveloppes bimanuelles présentent des contraintes de configuration répertoriées dans le tableau de prise en charge des robots.
Avec la prise en charge, un tampon vide peut conserver la dernière cible appliquée pendant action_starvation_grace_s (1 seconde par défaut), y compris le temps nécessaire pour évacuer les inférences anciennes et prédire à partir d’une capture fraîche. Une fois le mouvement démarré, une tolérance nulle ou un robot non pris en charge déclenche une erreur immédiate en cas d’épuisement ; le démarrage a sa propre échéance.
Contrairement à l’ancien chemin RTC local, l’épuisement n’est plus un tick indéfiniment ignoré. La tolérance ne prolonge jamais les limites de demande expirée ou de fraîcheur.
Local RTC expose ces budgets supplémentaires via --inference. :
| Paramètre | Par défaut | Signification |
|---|---|---|
max_observation_age_s | 5 s | Âge maximal de la source via l'exécution d'une action |
action_timeout_s | 10 s | Demande d'action en attente |
startup_timeout_s | 120 s | Préparation initiale et démarrage en action |
language_timeout_s | 120 s | Opération linguistique prévue |
Il s’agit de valeurs de départ, et non de garanties matérielles mesurées. RTC doit conserver suffisamment de lecture après ajustement pour une autre inférence et une marge de temporisation. Augmenter queue_threshold ne peut pas aider si l’inférence consomme régulièrement l’horizon de prédiction utilisable ; réduisez la latence ou validez un horizon plus long / une fréquence d’action différente.
Une défaillance terminale ne reprend jamais automatiquement le mouvement de la politique. Avec une acquisition d’observations utilisable et un contrôle des actionneurs, l’arrêt respecte --return_to_initial_position (valeur par défaut : true) et se déconnecte. Le retour lit une observation complète, de sorte qu’une défaillance persistante de la caméra peut l’empêcher. L’échec de l’application d’une commande supprime tout mouvement d’arrêt ultérieur pour tous les backends, y compris sync. Cela ne modifie pas la prédiction synchrone et n’introduit pas de récupération spécifique à la caméra.
Compilation and instruction changes
La compilation RTC réchauffe à la fois les appels initiaux et préfixés, y compris un délai non nul représentatif, avant le mouvement. Bien que --compile_warmup_inferences soit défini par défaut à 2, l’activation de RTC en exige au moins 3 ; les journaux de démarrage indiquent le nombre effectif. Le réchauffage doit tenir dans startup_timeout_s. Les instructions ultérieures qui modifient les formes des tenseurs peuvent déclencher une recompilation et dépasser la durée de lecture ou le délai d’une opération.
Un changement de tâche pendant l’inférence abandonne le résultat obsolète. Les robots pris en charge se mettent en pause et demandent de nouvelles actions ; à défaut de cette capacité d’attente, les actions restantes peuvent s’épuiser. Il est préférable de changer de tâche entre les exécutions sur ces robots.
Sessions interactives
Ajoutez --interactive=true pour piloter le déroulement depuis le terminal au lieu de démarrer immédiatement. Le matériel se connecte et la politique se charge comme d’habitude, mais le robot reste immobile jusqu’à ce que vous tapiez /start — pratique lorsque vous souhaitez d’abord positionner la scène, réinstruire la politique entre les tentatives, ou effectuer plusieurs prises sans payer à nouveau le temps de chargement.
lerobot-rollout \
--strategy.type=base \
--policy.path=${HF_USER}/my_smolvla_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
--task="pick up the cube" \
--interactive=true| Commande | Action |
|---|---|
/start | Démarrer (ou redémarrer) la boucle de contrôle de la politique |
/subtask <text> | Modifiez l'instruction au sein de l'exécution en cours ; l'inférence asynchrone peut entrer dans une pause planifiée. L'absence d'argument affiche la tâche actuelle. N'affecte que les politiques conditionnées sur le langage (SmolVLA, π0/π0.5 et similaires). Avec un planificateur externe, /subtask restitue également le contrôle depuis autosteer |
/vqa <text> | Posez une question sur ce que le robot voit actuellement. Uniquement pendant qu'une exécution est en cours, et uniquement avec une tête de politique texte ou un planificateur externe |
/autosteer <goal> | Indiquez un objectif de haut niveau et laissez la tête textuelle de la politique ou le planificateur externe choisir les sous-tâches. /autosteer off rend le contrôle ; l’absence d’argument signale l’objectif actuel |
/reset | Arrêter le mouvement, ramener le robot à sa position de démarrage et rétablir l'instruction --task |
/stop | Mettre fin à la session et exécuter les routines d'arrêt normales |
/help | Lister les commandes |
> /start Rollout running — task 'pick up the cube'. /subtask <text> to change it, ... > /subtask put the cube in the box Task: 'pick up the cube' → 'put the cube in the box' (applies from the next policy inference) > /vqa is the cube inside the box? Asked: 'is the cube inside the box?' — answering from the next observation... Q: is the cube inside the box? A: No — the cube is on the table to the left of the box. > /reset Task restored to 'pick up the cube' Resetting — returning the robot to its initial position... Robot reset — holding at initial position. /start to run. > /stop
Les lignes commençant par > sont celles que vous saisissez ; la session n’affiche aucun invite de commande propre.
Ctrl-C met toujours fin à l’exécution comme d’habitude, et la fermeture de l’entrée standard (Ctrl-D, ou la fin d’un script pipé) termine la session — un script pipé doit donc maintenir l’entrée standard ouverte pendant la durée prévue :
(printf '/start\n'; sleep 60; printf '/stop\n') | lerobot-rollout ... --interactive=true/subtask met à jour la tâche pour les inférences suivantes. Avec --inference.type=sync, les actions de blocs précalculées sont abandonnées. Avec une RTC locale ou une inférence distante, un changement survenant pendant une demande d’action entraîne l’abandon du résultat obsolète. Sur un robot prenant en charge la retenue locale, le moteur invalide le mouvement en file d’attente ou interpolé, attend que la boucle de contrôle confirme la retenue, puis demande un nouveau mouvement sous la nouvelle tâche. Un arrêt est à prévoir pendant cette transition ; il ne s’agit pas d’un fondu continu garanti. Un changement entre deux demandes peut conserver les actions déjà acceptées pour assurer la continuité. La RTC locale sur un robot sans prise en charge de la retenue abandonne toujours le résultat obsolète, mais peut épuiser ses actions restantes et se terminer. Modifiez les instructions entre les exécutions si ce robot ne peut pas retenir correctement.
Avec --use_torch_compile=true en local, une instruction qui modifie les formes des tenseurs traités peut déclencher une recompilation. Le padding peut maintenir certaines modifications de tâche à la même forme, mais une compilation ultérieure n’est pas garantie de s’adapter au reste de la lecture ou à l’échéance de modification d’instruction : cela peut provoquer une erreur fatale. Laissez la compilation désactivée lors de l’exploration d’instructions, sauf si ces transitions ont été validées. Les modifications pendant le réchauffement de la compilation initiale ne demandent pas de maintien du mouvement, car le mouvement de la politique n’a pas encore commencé.
/vqa et /autosteer nécessitent une politique qui déclare la prise en charge de la génération de texte et implémente PreTrainedPolicy.generate_text(), comme un checkpoint EO1 correctement entraîné, ou un planificateur externe. La capacité est vérifiée avant d’accepter une requête ; le comportement spécifique au checkpoint doit encore être validé.
/vqa répond à partir de l’observation suivante et affiche le résultat dès qu’il est prêt, ce qui vous permet de continuer à taper en attendant ; une seule question est en cours à la fois (les requêtes d’autosteer partagent ce créneau). Avec un planificateur externe, la question est traitée sur un thread de travail pendant que le robot continue d’agir, et rien de ce qui suit ne s’applique. Les backends asynchrones entrent d’abord dans un état local de suspension confirmé et invalident les mouvements en file d’attente ou interpolés, puis soumettent une nouvelle observation au chemin de texte. Une exécution active et saine reprend à partir d’un nouveau bloc d’actions après la réponse ; les exécutions en pause ou arrêtées restent inactives. La génération de texte synchrone s’exécute en ligne sur le thread de contrôle et abandonne les actions précalculées de la politique, de sorte que le tick suivant reprend à partir d’une inférence fraîche. Le temps de requête compte dans --duration. Les questions ne sont acceptées que lorsqu’une exécution est en cours — un moteur inactif n’a pas de vue actuelle. Si l’instruction ou le contexte d’exécution invalide une réponse asynchrone, la question reçoit un avis d’annulation au lieu de la réponse obsolète. La fin du segment signale également les questions sans réponse, y compris celles dont la génération est toujours en cours ; signaler une annulation n’interrompt pas un appel de modèle en cours ni ne libère son créneau de requête prématurément.
/autosteer confie un objectif à la politique et lui demande le sous-tâche suivante toutes les --autosteer_interval_s secondes (10 par défaut), en appliquant chacune via le même chemin que /subtask et en l’annonçant dans le chat. Chaque requête est une génération de texte complète, l’intervalle agit donc comme un réglage de coût : des valeurs plus basses déclenchent une replanification plus tôt, mais consomment davantage de la boucle à générer du texte plutôt qu’à agir. L’avancement du plan réside dans la politique, non dans le déroulement, si bien que le plan disparaît avec le segment ; le séquenceur s’arrête également sur /reset, /subtask, /autosteer off et la première erreur de planification.
Les journaux de niveau inférieur à ERROR sont masqués pendant l’exécution de la session afin que la sortie de routine ne se mélange pas à ce que vous tapez ; les erreurs et les échecs fatals d’inférence s’affichent toujours, et la journalisation normale reprend lorsque la session se termine. Les résultats de cadence sont imprimés dans le chat, et l’exécution sans --interactive vous redonne le journal en direct.
Les sessions fonctionnent via SSH et sur des machines sans interface graphique — aucun serveur d'affichage n'est nécessaire.
Enregistrement en mode interactif. --strategy.type=sentry accepte également --interactive=true et enregistre en continu pendant que vous pilotez : chaque segment /start…/reset enregistre des épisodes complets ainsi qu'un dernier épisode partiel, et le jeu de données reste ouvert jusqu'à l'arrêt. Les images portent la provenance de la tâche de l'action réellement envoyée, de sorte que les actions déjà en file d'attente ou interpolées sous l'ancienne tâche conservent l'ancien libellé.
Les sauvegardes d'épisodes bloquantes en cours d'exécution mettent en pause l'inférence asynchrone, invalident les actions en file d'attente ou interpolées et demandent une pause locale prise en charge. Elles repartent d'une capture fraîche uniquement si l'exécution reste active et saine. La même protection s'applique aux sauvegardes DAgger et aux vidages/sauvegardes du tampon circulaire Highlight. Les pauses d'enregistrement et les famines ordinaires partagent le comportement d'attente physique, mais leurs délais et leurs raisons de reprise restent distincts.
lerobot-rollout \
--strategy.type=sentry \
--policy.path=${HF_USER}/my_smolvla_policy \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--dataset.repo_id=${HF_USER}/rollout_cube_sessions \
--task="pick up the cube" \
--interactive=trueLes autres stratégies d’enregistrement (episodic, DAgger, highlight) ne sont pas prises en charge : elles lient leurs propres contrôles clavier et finalisent le jeu de données lorsque run() se termine, ce qui empêche de les relancer segment par segment.
Planificateur externe
--planner.model_id=<name> achemine /vqa et /autosteer via le client VLM du pipeline d’annotation (extra annotations) plutôt que via la tête textuelle de la politique. Les appels s’exécutent sur un thread de travail pendant que le robot continue d’agir, et aucun verrouillage linguistique n’est demandé pour eux, avec une exception délibérée : au démarrage de /autosteer, le backend d’inférence est mis en pause jusqu’à la première réponse du planificateur, car l’objectif constitue un contexte pour le planificateur et non une instruction pour la politique. Les backends asynchrones (rtc, remote) abandonnent les actions déjà mises en file ou en cours de calcul pour l’instruction précédente ; le backend synchrone retient le robot. Dès que la première sous-tâche est appliquée, le backend reprend à partir d’une observation fraîche. /subtask applique toujours votre instruction directement et annule la planification automatique ; les réponses tardives du planificateur ne peuvent pas la remplacer.
Le planificateur fonctionne avec tous les backends d’inférence. En inférence locale, il réutilise la recette de sous-tâches du processeur chargé si celle-ci existe, et demande sinon une instruction courte ; cela n’établit pas qu’un checkpoint peut la suivre. --planner.instructions='["pick up the cup","open the drawer"]' restreint la sortie du planificateur : le VLM est invité à choisir l’une de ces chaînes, et une réponse de sous-tâche suivante hors de la liste est une erreur. La casse, les espaces supplémentaires et un point final sont ignorés, et la formulation propre à la liste est transmise à la politique. Sans ce drapeau, la liste est lue à partir des étiquettes de tâches du jeu de données d’entraînement du checkpoint (via son train_config.json). Chaque étiquette est incluse dans le prompt, il est donc recommandé de passer le drapeau avec une liste plus courte pour les jeux de données comportant de nombreuses tâches. Si le jeu de données ne peut pas être lu, un avertissement est journalisé et la sortie du planificateur reste libre. L’inférence distante gère la politique, de sorte que ni la recette ni le vocabulaire d’entraînement ne sont disponibles localement : passez --planner.instructions explicitement, sinon les réponses restent libres. Le planificateur a été testé uniquement sur du matériel avec inférence locale.
Le planificateur externe reçoit les --planner.history dernières paires observation/commande (4 par défaut), suivies des images actuelles. Chaque paire contient l’observation utilisée pour demander une instruction et l’instruction acceptée par la suite par le moteur ; l’acceptation n’implique pas une exécution réussie. Les instructions répétées restent dans l’historique afin que le VLM puisse évaluer le manque de progression. L’historique est effacé lors d’une prise de contrôle manuelle, d’un réinitialisation, d’un arrêt, de la fin d’un segment ou d’un nouvel objectif autosteer. Définissez --planner.history=0 pour le désactiver. /vqa peut inspecter cet historique mais n’ajoute pas d’entrées. Pour une demande de sous-tâche suivante, le VLM doit d’abord décrire la scène et indiquer si la commande précédente est terminée, en cours ou en échec, et seulement ensuite nommer la prochaine instruction. Une réponse de done signifie que l’objectif est atteint : autosteer s’arrête, l’exécution se termine et le robot maintient sa pose jusqu’à /reset ou /start. Un done tardif, antérieur à un /subtask, /reset ou à un nouvel objectif, est ignoré. Chaque sous-tâche appliquée est affichée avec l’évaluation de la scène par le modèle et la latence de la requête.
Démarrer un serveur VLM compatible OpenAI séparément, puis se connecter :
transformers serve Qwen/Qwen3-VL-8B-Instruct --port 8000
# Alternatively: vllm serve Qwen/Qwen3-VL-8B-Instruct --port 8000
lerobot-rollout ... --task="pick up the cup" --interactive=true \
--planner.model_id=Qwen/Qwen3-VL-8B-Instruct \
--planner.api_base=http://localhost:8000/v1
> /start
> /vqa What is the next short instruction to clear the table?
> /subtask put the cup in the sink
> /autosteer clear the tableUn modèle hébergé fonctionne de la même manière. Cet exemple utilise les Hugging Face Inference Providers, avec un jeton ayant la permission « Make calls to Inference Providers » :
export HF_TOKEN=hf_...
lerobot-rollout ... --task="put all colored cubes in the green bin" --interactive=true \
--planner.model_id=Qwen/Qwen3.8-27B:novita \
--planner.api_base=https://router.huggingface.co/v1 \
--planner.api_key_env=HF_TOKEN/vqa ne renvoie qu’une réponse ; /autosteer applique des instructions. Configurez le point de terminaison avec --planner.api_base, et la clé avec --planner.api_key_env=<variable> (recommandé, la clé reste hors des commandes et des journaux) ou --planner.api_key. --planner.request_timeout_s (600 par défaut) et --planner.request_max_retries (2 par défaut) limitent chaque requête ; --planner.camera_key sélectionne une caméra ; --planner.log_path ajoute chaque échange en une ligne JSON. Les dépendances de service des modèles appartiennent à l’environnement du serveur. --autosteer_interval_s mesure le temps de mouvement du robot après l’application d’une réponse, en plus de la latence de la requête. La pilotage multi-niveaux et l’inventaire automatique des scènes sont hors du périmètre de cet adaptateur.
Contrôle programmatique
RolloutController expose les mêmes opérations qu’une API de bibliothèque — start() / reset() / stop() / set_task() sûrs pour le multithreading, ainsi qu’un rappel RolloutEvent, sans stdin, impression ni suppression de journaux associées :
from threading import Event, Thread
from lerobot.rollout import (
LinkedEvent,
RolloutController,
RolloutEvent,
build_rollout_context,
create_strategy,
)
parent = Event() # your application's shutdown signal
ctx = build_rollout_context(cfg, LinkedEvent(parent)) # loads policy, connects robot
strategy = create_strategy(cfg.strategy)
strategy.setup(ctx)
controller = RolloutController(strategy, ctx, on_event=print) # or your own observer
serve_thread = Thread(target=controller.serve) # serve() blocks; run it where you like
serve_thread.start()
controller.start() # robot starts executing the policy
controller.set_task("grab the red cube") # re-instruct mid-run
controller.reset() # stop movement, return home, stay warm
controller.stop() # end serve()
serve_thread.join()
strategy.teardown(ctx) # teardown stays with the callerRolloutController exige que l’événement d’arrêt du contexte soit un LinkedEvent, construit comme ci-dessus : le contrôleur met fin aux segments d’exécution via son drapeau local, tandis que votre événement parent force toujours un arrêt complet. Définissez play_sounds=False sauf si vous souhaitez les annonces vocales.
Options courantes
| Drapeau | Description | Par défaut |
|---|---|---|
--policy.path | Obligatoire. ID de modèle HF Hub ou chemin local du point de contrôle | — |
--robot.type | Obligatoire. Type de robot (par ex. so100_follower, koch_follower) | — |
--robot.port | Port série pour le robot | — |
--robot.cameras | Configuration de la caméra (dictionnaire JSON) | — |
--fps | Fréquence d'inférence de la politique et d'enregistrement du jeu de données | 30 |
--status_interval_s | Secondes entre les journaux de santé (0 désactive ; uniquement en mode non interactif) | 5 |
--duration | Temps d'exécution en secondes (0 = infini ; par segment si interactif) | 0 |
--device | Appareil Torch (cpu, cuda, mps) | auto |
--task | Description de la tâche (utilisée lorsqu’aucun jeu de données n’est fourni) | — |
--display_data | Diffuser la télémétrie vers la visualisation Rerun | false |
--display_ip / --display_port | Adresse du serveur Rerun distant | — |
--interpolation_multiplier | Commandes robot par action de politique (contrôle à fps × N Hz) | 1 |
--interactive | Session de chat sur stdin ; le robot reste inactif jusqu'à /start (base, sentry) | false |
--use_torch_compile | Activer torch.compile pour l'inférence | false |
--resume | Reprendre une session d’enregistrement précédente | false |
--play_sounds | Synthèse vocale pour les événements | true |
Rapports Cadence
Non-interactive rollout enregistre son statut toutes les cinq secondes. Modifiez l'intervalle avec --status_interval_s=10, ou désactivez-le avec --status_interval_s=0.
Rollout: loop 29.7/30 Hz · over budget 2/149 · starved 0 · infer 18.7 ms (worst 40.1) · process 2.41 GiB · GPU 5.12/79.6 GiB
La latence est la médiane/la pire sur l'intervalle (turnaround pour RTC/distant). Les champs indisponibles sont omis ; la mémoire GPU est globale à l'appareil, et la mémoire distante est locale. Le rapport est en pause lorsque la boucle est bloquée.
Chaque stratégie signale ce que sa boucle de contrôle a réellement accompli, tout comme lerobot-record, lerobot-teleoperate et lerobot-replay — ils suivent tous le même chronomètre. Chaque épisode enregistré reçoit un résumé en une ligne, et lorsque la boucle se termine — y compris en cas de Ctrl-C — un bloc couvrant l’exécution complète est affiché :
Cadence (episode 1): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/598 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence (episode 2): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/599 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence summary — whole run, 2 episodes · target 30 Hz × 2 (16.7 ms tick slot, 33.3 ms cycle budget): 2400 ticks, 1197 cycles judged
effective cadence: 29.84 Hz policy / 59.67 Hz commands over 40.2 s measured
cycles over the 33.3 ms work budget: 12/1197 (1.0%) — work mean 18.7 ms, worst 48.1 ms
ticks over their 16.7 ms slot: 40/2400 (costs interpolation smoothness only)
loop-body steps (share of measured work):
observe mean 3.13 ms · worst 9.00 ms · 31.7% of work · 2400 calls
process_obs mean 0.50 ms · worst 1.00 ms · 5.1% of work · 2400 calls
infer mean 5.18 ms · worst 40.00 ms · 52.4% of work · 2400 calls
send mean 0.40 ms · worst 0.40 ms · 4.1% of work · 2400 calls
record mean 0.67 ms · worst 620.00 ms · 6.8% of work · 2402 calls
pacing headroom: 7.4 ms slept per tick on average (max 13.0 ms) — near zero means the loop is saturatedLisez-le de haut en bas : la cadence effective est ce que vous avez obtenu par rapport à --fps, les cycles au-delà du budget indiquent à quelle fréquence la boucle n’a pas pu la maintenir, la décomposition par étape montre où le temps a été passé, et une marge de rythme proche de zéro signifie qu’il ne reste rien pour absorber un tick lent. Une part élevée de infer pointe vers la politique ou le périphérique ; une part élevée de observe pointe vers les caméras. Un worst d’une étape très supérieur à sa mean est un cas isolé — ci-dessus, record est de 0,67 ms par image et la valeur aberrante de 620 ms est le save_episode bloquant qui a clôturé un épisode.
Un cycle correspond ici à la durée d’une action de politique (--interpolation_multiplier ticks). Seuls les cycles entiers qui dépassent le budget de 1/fps peuvent entraîner une perte d’images ; un tick qui dépasse son créneau rend simplement le mouvement interpolé moins fluide, ce qui explique pourquoi les deux sont comptés séparément. Sans interpolation, un cycle est un tick unique, le rapport supprime donc cette distinction et compte les ticks dans l’ensemble.
Dans les sessions interactives, chaque segment /start possède son propre minuteur, de sorte que le rapport est envoyé dans le chat une fois par segment (/reset, /stop ou --duration) plutôt qu'une fois par processus.
Utilisation programmatique
Pour les extensions, lerobot.inference est le package public pour les configurations de backend, les moteurs locaux et les contrats de planification partagés. lerobot.remote_inference gère le moteur distant, le protocole, le déploiement et l’implémentation des sessions ; lerobot.rollout gère la boucle robot, les stratégies, l’interpolation et l’enregistrement. Importez les symboles publics depuis leur package plutôt que depuis les fichiers d’implémentation. Les dépendances distantes optionnelles ne sont nécessaires que lorsque le backend distant ou le serveur est utilisé.
Pour les déploiements personnalisés (par exemple avec des processeurs de cinématique), utilisez directement l’API du module de déroulement :
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
from lerobot.inference import SyncInferenceConfig
from lerobot.rollout.strategies import BaseStrategy
from lerobot.utils.process import ProcessSignalHandler
cfg = RolloutConfig(
robot=my_robot_config,
policy=my_policy_config,
strategy=BaseStrategyConfig(),
inference=SyncInferenceConfig(),
fps=30,
duration=60,
task="my task",
)
signal_handler = ProcessSignalHandler(use_threads=True)
ctx = build_rollout_context(
cfg,
signal_handler.shutdown_event,
robot_action_processor=my_custom_action_processor, # optional
robot_observation_processor=my_custom_obs_processor, # optional
)
strategy = BaseStrategy(cfg.strategy)
try:
strategy.setup(ctx)
strategy.run(ctx)
finally:
strategy.teardown(ctx)Consultez examples/so100_to_so100_EE/rollout.py et examples/phone_to_so100/rollout.py pour des exemples complets avec des processeurs de cinématique.