LeRobot documentation
Ajout d'une politique
Ajout d'une politique
Ce guide vous guide à travers l'implémentation d'une politique personnalisée et sa mise en œuvre avec les outils d'entraînement, d'évaluation et de déploiement de LeRobot. Il existe deux approches :
- Plugin (hors de l’arborescence) — publiez votre politique sous forme de package autonome
lerobot_policy_*. Plus rapide, aucune PR requise, itération facile. Idéal pour l’expérimentation, un usage interne ou si vous souhaitez publier de manière indépendante. - Intégré (contribué à LeRobot) — intégrez votre politique directement dans
src/lerobot/policies/. Nécessite une PR, mais fait de votre politique un citoyen de premier ordre de la bibliothèque.
La route du plugin est généralement le bon point de départ — passez-la dans l’arborescence une fois que la politique s’est stabilisée et qu’il y a une valeur claire à la livrer avec la bibliothèque.
Dans tous les cas, les briques de base sont les mêmes : une classe de configuration, une classe de politique et une fabrique de processeurs. La première moitié de ce guide aborde ces éléments partagés ; la seconde moitié couvre le squelette spécifique à chaque chemin (Path A, Path B).
Remarque sur le ton : robot-learning est un domaine en évolution active, et l’aspect d’une politique peut changer avec chaque nouvelle architecture. Les conventions décrites ici existent car elles permettent à lerobot-train et lerobot-eval de fonctionner de manière uniforme sur des modèles très différents. Si une nouvelle politique ne s’y adapte réellement pas, signalez-le (dans votre PR ou dans une issue) — les conventions ne sont pas sacrées.
Anatomie d'une politique
Trois briques de base composent chaque politique. Les noms ci-dessous utilisent my_policy comme espace réservé — remplacez-le par le nom de votre politique. Ce nom est déterminant : il doit correspondre à la chaîne passée à @PreTrainedConfig.register_subclass, à l’attribut de classe MyPolicy.name et à la fonction de fabrique make_<name>_pre_post_processors (plus de détails sur chacun ci-dessous).
Classe de configuration
Hériter de PreTrainedConfig et enregistrer votre type de politique. Voici un modèle — personnalisez les paramètres et les méthodes selon l’architecture de votre politique et ses besoins en matière d’entraînement.
# configuration_my_policy.py
from dataclasses import dataclass, field
import torch
from lerobot.configs import PreTrainedConfig
from lerobot.optim import AdamWConfig
from lerobot.optim import CosineDecayWithWarmupSchedulerConfig
@PreTrainedConfig.register_subclass("my_policy")
@dataclass
class MyPolicyConfig(PreTrainedConfig):
"""Configuration class for MyPolicy.
Args:
n_obs_steps: Number of observation steps to use as input
horizon: Action prediction horizon
n_action_steps: Number of action steps to execute
hidden_dim: Hidden dimension for the policy network
# Add your policy-specific parameters here
"""
dtype: torch.dtype | None = torch.bfloat16
horizon: int = 50
n_action_steps: int = 50
hidden_dim: int = 256
optimizer_lr: float = 1e-4
optimizer_weight_decay: float = 1e-4
def __post_init__(self):
super().__post_init__()
if self.n_action_steps > self.horizon:
raise ValueError("n_action_steps cannot exceed horizon")
def validate_features(self) -> None:
"""Validate input/output feature compatibility.
Call this explicitly from your policy's __init__ — the base class does not.
"""
if not self.image_features:
raise ValueError("MyPolicy requires at least one image feature.")
if self.action_feature is None:
raise ValueError("MyPolicy requires 'action' in output_features.")
def get_optimizer_preset(self) -> AdamWConfig:
return AdamWConfig(lr=self.optimizer_lr, weight_decay=self.optimizer_weight_decay)
def get_scheduler_preset(self):
"""Return a LRSchedulerConfig from lerobot.optim, or None."""
return None
@property
def observation_delta_indices(self) -> list[int] | None:
"""Relative timestep offsets the dataset loader provides per observation.
Return `None` for single-frame policies. For temporal policies that consume
multiple past or future frames, return a list of offsets, e.g. `[-20, -10, 0, 10]` for
3 past frames at stride 10 and 1 future frame at stride 10.
"""
return None
@property
def action_delta_indices(self) -> list[int]:
"""Relative timestep offsets for the action chunk the dataset loader returns."""
return list(range(self.horizon))
@property
def reward_delta_indices(self) -> None:
return NoneLa chaîne de caractères que vous passez à @register_subclass doit correspondre à MyPolicy.name (section suivante) et est ce que les utilisateurs fournissent en tant que --policy.type sur l'interface en ligne de commande. Par défaut, utilisez AdamW de lerobot.optim pour get_optimizer_preset, sauf si vous en avez réellement besoin autrement. PreTrainedConfig déclare déjà dtype: torch.dtype | None ; pour donner à votre politique une précision par défaut, redéclarez-la avec une valeur torch.dtype (par exemple dtype: torch.dtype | None = torch.bfloat16).
Classe de politique
Hériter de PreTrainedPolicy et définir deux attributs de classe — les deux sont vérifiés par __init_subclass__ :
# modeling_my_policy.py
import torch
import torch.nn as nn
from typing import Any
from lerobot.policies import PreTrainedPolicy
from lerobot.utils.constants import ACTION
from .configuration_my_policy import MyPolicyConfig
class MyPolicy(PreTrainedPolicy):
config_class = MyPolicyConfig # must match the string in @register_subclass
name = "my_policy"
def __init__(self, config: MyPolicyConfig, dataset_stats: dict[str, Any] = None):
super().__init__(config, dataset_stats)
config.validate_features() # not called automatically by the base class
self.config = config
self.model = ... # your nn.Module here
def reset(self):
"""Reset per-episode state. Called by lerobot-eval at the start of each episode."""
...
def get_optim_params(self) -> dict:
"""Return parameters to pass to the optimizer (e.g. with per-group lr/wd)."""
return {"params": self.parameters()}
def predict_action_chunk(self, batch: dict[str, torch.Tensor], **kwargs) -> torch.Tensor:
"""Return the full action chunk (B, chunk_size, action_dim) for the current observation."""
...
def select_action(self, batch: dict[str, torch.Tensor], **kwargs) -> torch.Tensor:
"""Return a single action for the current timestep (called every step at inference)."""
...
def forward(self, batch: dict[str, torch.Tensor]) -> tuple[torch.Tensor, dict | None]:
"""Compute the training loss.
Returns `(loss, output_dict)`. `output_dict` may be `None`; everything in it must be
logging-friendly Python natives (no tensors with gradients).
`batch["action_is_pad"]` is a bool mask of shape (B, horizon) that marks
timesteps padded because the episode ended before `horizon` steps; you
can exclude those from your loss.
"""
actions = batch[ACTION]
action_is_pad = batch.get("action_is_pad")
...
return loss, {"some_loss_component": some_loss_component.item()}Les méthodes appelées par les boucles d'entraînement/évaluation :
| Méthode | Utilisé par | Ce qu'il fait |
|---|---|---|
reset() -> None | lerobot-eval | Effacer l'état par épisode au début de chaque épisode. |
select_action(batch, **kwargs) -> Tensor | lerobot-eval | Retourne la prochaine action (B, action_dim). Appelé à chaque étape. |
predict_action_chunk(batch, **kwargs) -> Tensor | la politique elle-même | Retourner un chunk d’actions (B, chunk_size, action_dim). Actuellement abstrait dans la classe de base — lever NotImplementedError si votre politique ne chunk pas. |
forward(batch, reduction="mean") -> tuple[Tensor, dict \| None] | lerobot-train | Retourne (loss, output_dict). Accepte reduction="none" si vous souhaitez prendre en charge le pondération par échantillon. |
get_optim_params() -> dict | l'optimiseur | Retourne self.parameters() pour les politiques simples ; retourne un dictionnaire de paramètres nommés pour les politiques multi-optimisateurs (voir get_optim_params dans modeling_act.py pour un exemple de taux d'apprentissage par groupe). |
update() -> None (facultatif) | lerobot-train | Appelé après chaque étape de l'optimiseur si défini. À utiliser pour l'EMA, les réseaux cibles et les tampons de replay (TDMPC l'utilise). |
Les lots sont des dictionnaires plats indexés par les constantes de lerobot.utils.constants : OBS_STATE (observation.state.<motor>), OBS_IMAGES (observation.images.<camera>), OBS_LANGUAGE, ACTION, etc. Réutilisez les constantes — n’inventez pas de nouveaux préfixes.
Si votre modèle est suffisamment volumineux pour justifier un entraînement multi-GPU avec fragmentation, déclarez également ses unités d’encapsulation FSDP, c’est-à-dire les classes de blocs répétés sur lesquelles s’applique la fragmentation :
class MyPolicy(PreTrainedPolicy):
...
_fsdp_wrap_modules = ["MyTransformerBlock"]Avec cette seule déclaration, --parallelism.dp_shard=N fonctionne immédiatement pour votre politique (les utilisateurs peuvent toujours le remplacer avec --accelerator.fsdp.wrap_modules). En l’absence de source d’emballage, les exécutions shardées échouent au démarrage par conception.
Fonctions de processeur
LeRobot utilise des PolicyProcessorPipeline pour normaliser les entrées et dénormaliser les sorties autour de votre politique. Pour une référence concrète, consultez processor_act.py ou processor_diffusion.py.
Faites particulièrement attention ici : les processeurs sont la source de problèmes de reproductibilité la plus courante. Un écart dans le mode de normalisation (IDENTITY par rapport à MEAN_STD, MIN_MAX ou QUANTILES/QUANTILE10) ou dans les fonctionnalités qui sont normalisées entraînera un entraînement et une évaluation sans erreur, mais ruinera silencieusement les résultats. Assurez-vous que les modes correspondent à la manière dont le point de contrôle a été entraîné, que les statistiques requises existent (par exemple, QUANTILES nécessite q01/q99) et que les pré-processeurs et post-processeurs restent cohérents.
# processor_my_policy.py
from typing import Any
import torch
from lerobot.processor import PolicyAction, PolicyProcessorPipeline
def make_my_policy_pre_post_processors(
config,
dataset_stats: dict[str, dict[str, torch.Tensor]] | None = None,
) -> tuple[
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
PolicyProcessorPipeline[PolicyAction, PolicyAction],
]:
preprocessor = ... # build your PolicyProcessorPipeline for inputs
postprocessor = ... # build your PolicyProcessorPipeline for outputs
return preprocessor, postprocessorImportant — nommage des fonctions : LeRobot découvre votre processeur par son nom. La fonction doit s'appeler make_{policy_name}_pre_post_processors (correspondant à la chaîne passée à @PreTrainedConfig.register_subclass).
Facultatif : ajustement des processeurs chargés depuis un point de contrôle
make_{policy_name}_pre_post_processors ne s’exécute que lorsque les processeurs sont construits à partir de zéro. Chaque fois qu’un point de contrôle est impliqué (ajustement fin, reprise ou évaluation), LeRobot charge à la place les pipelines sérialisés situés à côté des poids et applique les substitutions de l’appelant.
Si votre politique doit ajuster ces pipelines chargés, ajoutez une seconde fonction facultative au même module, nommée make_{policy_name}_pre_post_processors_from_pretrained. LeRobot la trouve selon la même convention de nommage et l’appelle à la place du chargement générique. Les politiques qui n’ont besoin d’aucun traitement particulier peuvent simplement l’omettre.
from lerobot.processor import load_pretrained_policy_processors
from lerobot.utils.constants import (
POLICY_POSTPROCESSOR_DEFAULT_NAME,
POLICY_PREPROCESSOR_DEFAULT_NAME,
)
def make_my_policy_pre_post_processors_from_pretrained(
config,
pretrained_path: str,
*,
revision: str | None = None,
dataset_stats: dict[str, dict[str, torch.Tensor]] | None = None,
dataset_meta: Any | None = None,
preprocessor_overrides: dict[str, Any] | None = None,
postprocessor_overrides: dict[str, Any] | None = None,
preprocessor_config_filename: str = f"{POLICY_PREPROCESSOR_DEFAULT_NAME}.json",
postprocessor_config_filename: str = f"{POLICY_POSTPROCESSOR_DEFAULT_NAME}.json",
) -> tuple[
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
PolicyProcessorPipeline[PolicyAction, PolicyAction],
]:
preprocessor, postprocessor = load_pretrained_policy_processors(
pretrained_path,
revision=revision,
preprocessor_overrides=preprocessor_overrides,
postprocessor_overrides=postprocessor_overrides,
preprocessor_config_filename=preprocessor_config_filename,
postprocessor_config_filename=postprocessor_config_filename,
)
... # reconcile the loaded pipelines with the current `config`
return preprocessor, postprocessorPour des exemples concrets, consultez processor_evo1.py (ré-ajuste les statistiques de normalisation), processor_groot.py (gère les points de contrôle bruts en amont) et processor_molmoact2.py (renomme les clés de substitution).
Retourner une prédiction en plus des actions (images imaginées, sous-tâches, boîtes)
Une politique peut retourner, en plus de l’action qu’elle exécute, ce qu’elle attend ou croit, comme les images imaginées par un modèle du monde, une sous-tâche prédite ou des boîtes englobantes. L’option --display_data permet de les afficher en direct dans Rerun ou Foxglove. Les fonctions select_action et predict_action_chunk prennent un lot en entrée et retournent soit un tenseur d’actions brut (le comportement par défaut de toute politique), soit un PolicyOutput (lerobot.lerobot_types) : un lot, indexé comme le lot d’entrée, contenant l’action sous la clé "action" et une PolicyPrediction sous la clé "prediction". La prédiction est lotée comme l’action, avec une valeur par environnement, et chaque entrée est indexée par ce à quoi elle fait référence :
observation: l'observation future prédite, par clé d'observation ("observation.images.top"->[B, C, H, W]images). Elle est affichée sous le nomprediction.images.top.language: une chaîne par environnement, par style d’annotation ("subtask","plan","memory"), les styles des colonnes de langue du pipeline d’annotation. Il est affiché sous la formeprediction.subtask.boxes: par la clé de l’image d’observation sur laquelle elles sont dessinées, une réponse bbox par environnement dans le schéma VQA d’annotation ({"detections": [{"label", "bbox": [x1, y1, x2, y2]}]}, fractions d’image de 0 à 1).
Les appelants normalisent les deux types de retour avec policy_output_to_transition, qui convertit un PolicyOutput en EnvTransition utilisé par les processeurs, de sorte que seules les politiques qui prédisent quelque chose changent. Les visualiseurs affichent le premier environnement.
from lerobot.utils.constants import ACTION, PREDICTION
class MyPolicy(PreTrainedPolicy):
def select_action(self, batch, **kwargs) -> Tensor | PolicyOutput:
if not self._action_queue:
chunk, extras = self.model.sample(batch)
self._action_queue.extend(chunk.transpose(0, 1))
if self.config.return_prediction: # a config field of your choosing
# The prediction goes with the step that computed it; the others return a bare action.
prediction = {
"observation": {"observation.images.top": extras.next_frames}, # [B, C, H, W]
"language": {"subtask": extras.subtasks}, # list[str], one per env
"boxes": {
"observation.images.top": [
{"detections": [{"label": "cube", "bbox": xyxy}]} for xyxy in extras.cube_xyxy
]
},
}
return {ACTION: self._action_queue.popleft(), PREDICTION: prediction}
return self._action_queue.popleft()- Une prédiction contient les valeurs pour cette étape. Celle qui s'étend sur un bloc, comme un clip imaginé, est renvoyée morceau par morceau : chaque étape renvoie la partie correspondant à l'action qu'elle sert.
- Chaque entrée reste affichée jusqu’à ce qu’elle soit retournée. Renvoyez des
detectionsvides pour effacer les boîtes d’une caméra. - La prédiction est activée manuellement via la configuration de votre politique et ne doit engendrer aucun coût lorsqu'elle est désactivée.
- Une prédiction ne modifie jamais les actions, et seule l'action passe par le postprocesseur. Le texte
qui doit orienter la politique, comme la sous-tâche suivante, passe par
generate_text(ci-dessous).
Politiques capables de texte
Les politiques de langue partagent le rendu de la recette entre deux étapes de processeur :
RenderTrainingMessagesStepconvertit les annotations de langue du jeu de données en messages rendus.RenderRuntimeMessagesStepconvertit les invites utilisateur en messages rendus.
Les processeurs spécifiques à la politique appliquent ensuite le formatage de la conversation et la tokenisation.
La configuration de la politique stocke la recipe: TrainingRecipe | None résolue (provenant de lerobot.datasets.recipe). Un recipe_path facultatif peut charger une recette YAML ;
résolvez-la avant de construire les processeurs. Une recette résolue active l’entraînement par recette, et None rend le rendu d’entraînement inopérant. Sérialisez la recette
résolue dans la configuration et le processeur afin que le déploiement ne dépende pas d’un chemin YAML local.
Le rendu basé sur les recettes nécessite l’extra dataset car les recettes sont actuellement
situées sous lerobot.datasets.
Placez les deux renderers avant le formatage de chat et la tokenisation natifs de la policy :
input_steps = [
RenderRuntimeMessagesStep(config.recipe),
RenderTrainingMessagesStep(config.recipe),
# observation transforms
# policy-native roles, image placement, chat template, and tokenization
# device placement
]Le même pipeline est enregistré et chargé pour l’entraînement et l’inférence. Le rendu d’exécution ne s’effectue que pour un query_kind explicite. Le rendu d’entraînement ignore les requêtes d’exécution et, lorsqu’une recette est configurée, traite les échantillons avec des annotations brutes ou des cibles d’action. Les entrées d’observation ordinaires passent sans modification. L’entraînement sur du texte annoté uniquement est pris en charge sans tenseur d’action. L’entraînement sans annotations nécessite des cibles d’action pour activer le repli de tâche.
L'usine partagée ne construit ou ne charge que des processeurs. L'ajustement fin des recettes de langage les construit à partir de la configuration active et des statistiques du jeu de données, de sorte qu'un processeur enregistré ne puisse pas écraser la nouvelle recette. La reprise et l'inférence chargent les pipelines enregistrés, y compris leur recette et leurs statistiques. L'entraînement non linguistique conserve son comportement existant de construction et de chargement de points de contrôle. Les usines neuves dérivent elles-mêmes les paramètres de normalisation et d'action relative. Les usines neuves utilisent l'appareil configuré ; le prétraitement par lot du jeu de données existant applique la carte de renommage d'entraînement.
messages_rendered contient les messages sémantiques développés par la recette, avant le formatage de chat natif ou la tokenisation. Les fichiers d’accompagnement de l’entraînement indexent cette même liste. Les utilitaires de messages se trouvent dans lerobot.utils.language.
Le déroulement interactif ajoute query_kind et query_text. Le rendu à l’exécution prend en charge :
vqa: conserve la question de l'appelant en tant que message utilisateur.next_subtask: rendre la recette de point de contrôle jusqu'à, mais sans inclure, le tour de l'assistant qui supervise${subtask}.
La politique expose ensuite son décodeur via l'API de politique existante :
class MyPolicy(PreTrainedPolicy):
def supports_text_generation(self) -> bool:
return True
def generate_text(self, batch) -> str:
token_ids = self.model.generate(**batch)
return self.tokenizer.decode(token_ids, skip_special_tokens=True)generate_text consomme la sortie du processeur prête pour le modèle ; il ne doit pas reconstruire ni retokenizer le prompt de l’appelant. Lors de l’entraînement, le tokeniseur natif de la politique utilise target_message_indices pour superviser uniquement les segments d’assistant sélectionnés. Stockez la recette dans la configuration de la politique de langage afin qu’elle soit sérialisée avec le checkpoint et que le déploiement reconstruise le prompt utilisé pour l’entraînement.
Chemin A : Plugin hors de l'arborescence
La méthode la plus rapide pour déployer une politique : l’empaqueter en tant que distribution Python autonome et l’installer aux côtés de LeRobot. Aucune demande de fusion n’est requise, vous maîtrisez le cycle de publication et vous pouvez publier sur PyPI sous votre propre espace de noms.
Structure du package
Créez un package avec le préfixe lerobot_policy_ (IMPORTANT !) suivi du nom de votre politique :
lerobot_policy_my_policy/
├── pyproject.toml
└── src/
└── lerobot_policy_my_policy/
├── __init__.py
├── configuration_my_policy.py
├── modeling_my_policy.py
└── processor_my_policy.pypyproject.toml
[project]
name = "lerobot_policy_my_policy"
version = "0.1.0"
dependencies = [
# your policy-specific dependencies
]
requires-python = ">= 3.12"
[build-system]
build-backend = # your-build-backend
requires = # your-build-systemPackage __init__.py
Exposez vos classes dans le fichier __init__.py du package et protégez-vous contre l'absence de lerobot :
# __init__.py
"""Custom policy package for LeRobot."""
try:
import lerobot # noqa: F401
except ImportError:
raise ImportError(
"lerobot is not installed. Please install lerobot to use this policy package."
)
from .configuration_my_policy import MyPolicyConfig
from .modeling_my_policy import MyPolicy
from .processor_my_policy import make_my_policy_pre_post_processors
__all__ = [
"MyPolicyConfig",
"MyPolicy",
"make_my_policy_pre_post_processors",
]Installer et utiliser
cd lerobot_policy_my_policy
pip install -e .
# Or install from PyPI if published
pip install lerobot_policy_my_policyUne fois installé, votre policy s’intègre automatiquement aux outils d’entraînement et d’évaluation de LeRobot :
lerobot-train \
--policy.type my_policy \
--dataset.repo_id lerobot/pusht \
--env.type pusht \
--steps 200000 \
--policy.push_to_hub falseChemin B : Contribuer dans l'arborescence
Une fois que votre politique est stabilisée et qu’il est clairement utile de l’intégrer à la bibliothèque, vous pouvez la soumettre directement à LeRobot. Lisez d’abord le guide de contribution général et le modèle de PR — c’est là que vous trouverez les attentes en matière de tests et de qualité que chaque PR doit respecter (pre-commit run -a, pytest, la règle de revue par la communauté, etc.). Ce qui suit est la couche spécifique à la politique qui s’ajoute à cela.
Disposition dans l'arborescence
src/lerobot/policies/my_policy/
├── __init__.py # re-exports config + modeling + processor factory
├── configuration_my_policy.py # MyPolicyConfig + @register_subclass
├── modeling_my_policy.py # MyPolicy(PreTrainedPolicy)
├── processor_my_policy.py # make_my_policy_pre_post_processors
└── README.md # symlink → ../../../../docs/source/policy_my_policy_README.mdDeux remarques :
- Le
README.mdsitué à côté du code source est un symlink versdocs/source/policy_<name>_README.md— le fichier réel se trouve sousdocs/. Les politiques existantes (act, smolvla, diffusion, …) fonctionnent toutes ainsi ; copiez l’un de ces symlinks. Le README de la politique est conventionnellement minimal : lien vers l’article + citation BibTeX. - Le tutoriel destiné aux utilisateurs — ce qu’il faut installer, comment entraîner, les hyperparamètres, les chiffres de référence — est conservé séparément dans
docs/source/<my_policy>.mdxet est enregistré dans_toctree.ymlsous « Policies ».
Les noms de fichiers sont déterminants : la fabrique effectue des imports paresseux par nom, et le processeur est découvert selon la convention make_<policy_name>_pre_post_processors.
Câblage
Deux endroits doivent connaître votre politique. Tous par leur nom.
policies/__init__.py— réexportezMyPolicyConfiget ajoutez-le à__all__. C’est cet import qui enregistre votre politique :@PreTrainedConfig.register_subclass("my_policy")s’exécute, et dès lors la fabrique résout tout par convention. Ne réexportez pas la classe de modélisation ; elle est chargée paresseusement via la fabrique (afin queimport lerobotreste rapide).templates/lerobot_modelcard_template.mdet leREADME.mdracine — le modèle est ce que le publieur de fin d’entraînement rend dans la carte de modèle de chaque point de contrôle entraîné avec votre politique : ajoutez une description en une ligne de votre politique dans les branchesmodel_name, mappez-la danspolicy_docspour que les cartes lient vers votre guide MDX, et ajoutez éventuellement une image d’architecture dansdiagrams. Ajoutez ensuite votre politique au tableau des modèles dans leREADME.mdracine, sous la bonne catégorie, en liant vers votre page de documentation.
Miroir d’une politique existante structurellement similaire à la vôtre ; la différence est minime.
Dépendances lourdes / facultatives
La plupart des politiques nécessitent un backbone lourd (transformers, diffusers, un SDK VLM spécifique). Là où une implémentation existe, privilégiez son chargement, par exemple depuis transformers ou diffusers, plutôt que de réimplémenter l'architecture dans l'arborescence du projet.
La convention est le gating en deux étapes : une importation protégée par TYPE_CHECKING en haut du module, et une vérification d’exécution require_package dans le constructeur. modeling_diffusion.py est la référence canonique :
from typing import TYPE_CHECKING
from lerobot.utils.import_utils import _diffusers_available, require_package
if TYPE_CHECKING or _diffusers_available:
from diffusers.schedulers.scheduling_ddim import DDIMScheduler
else:
DDIMScheduler = None # keeps the symbol bindable at import time
class DiffusionPolicy(PreTrainedPolicy):
def __init__(self, config):
require_package("diffusers", extra="diffusion")
super().__init__(config)
...De cette façon :
import lerobot.policiescontinue de fonctionner sans l’installation supplémentaire (le symbole est simplement lié àNone).- Les vérificateurs de types voient le symbole réel.
- L’instantiation de la politique sans l’extension supplémentaire déclenche une
ImportErrorclaire pointant verspip install 'lerobot[diffusion]'.
Ajoutez un extra correspondant à pyproject.toml dans [project.optional-dependencies] et incluez-le dans l’extra all afin que pip install 'lerobot[all]' continue d’installer tout le contenu.
Évitez de copier un fichier de modélisation — sous-classez-le
Si votre politique doit modifier un backbone qui existe déjà dans transformers (conditionnement personnalisé, entrées supplémentaires, remplacement d'un sous-module), ne créez pas une copie de son modeling_*.py. Subclassez plutôt la plus petite unité amont et ne surchargez que ce qui change. pi_gemma.py est la référence canonique : il injecte le conditionnement AdaRMS dans PaliGemma/Gemma en ~370 lignes en sous-classant GemmaModel/PaliGemmaModel et en surchargeant le forward de la couche décodeur, au lieu de forker le fichier de modélisation d'environ 2 000 lignes. La chirurgie sur un modèle natif chargé est également acceptable (troncature de couches, extension du tokenizer, capture des états cachés — voir evo1/internvl3_embedder.py, eo1/modeling_eo1.py, groot/groot_n1_7.py pour des exemples fonctionnels). Les relecteurs exigeront ce schéma lorsqu'un PR arrive avec un fichier de modélisation copié ; la seule exception acceptée est un modèle qui n'existe tout simplement pas dans transformers.
Benchmarks et point de contrôle publié
Une nouvelle politique est beaucoup plus facile à examiner — et bien plus utile — lorsqu'elle est livrée avec un point de contrôle fonctionnel et au moins un chiffre que vous pouvez reproduire.
Choisissez au moins un benchmark intégré. LeRobot fournit des benchmarks de simulation avec des images Docker par benchmark (LIBERO, LIBERO-plus, Meta-World, RoboTwin 2.0, RoboCasa365, RoboCerebra, RoboMME, VLABench et d’autres). Choisissez celui qui correspond à la modalité de votre politique — les VLAs se tournent généralement vers LIBERO ou VLABench ; le BC image-only vers LIBERO ou Meta-World. La liste complète se trouve sous Benchmarks dans la barre latérale de la documentation.
Poussez le checkpoint et les processeurs sur le Hub sous lerobot/<policy>_<benchmark> (ou votre espace de noms si vous n’avez pas les droits d’écriture ; un mainteneur peut le répliquer). La méthode la plus simple consiste à entraîner avec --policy.repo_id=<namespace>/<repo> et --policy.push_to_hub=true : lerobot-train publie le modèle, les deux processeurs et une carte de modèle à la fin de l’exécution. Pour publier un checkpoint existant a posteriori, téléversez son répertoire pretrained_model/ (par ex. hf upload), ou utilisez lerobot-convert-dcp --push_to_hub=... pour les checkpoints au format fragmenté.
Signalez les résultats dans le MDX de votre politique, avec la commande lerobot-eval exacte et le matériel utilisé, afin que chacun puisse la relancer :
## Results
Evaluated on LIBERO with `lerobot/<policy>_libero`:
| Suite | Success rate | n_episodes |
| -------------- | -----------: | ---------: |
| libero_spatial | 87.5% | 50 |
| libero_object | 93.0% | 50 |
| libero_goal | 81.5% | 50 |
| libero_10 | 62.0% | 50 |
| **average** | **81.0%** | 200 |
Reproduce: `lerobot-eval --policy.path=lerobot/<policy>_libero --env.type=libero --env.task=libero_spatial --eval.n_episodes=50` (1× A100 40 GB).Utilisez n_episodes ≥ 50 par suite pour des estimations stables du taux de réussite.
Si votre politique est réservée aux robots réels et qu’aucun benchmark de simulation ne s’applique, remplacez l’évaluation en simulation par : un jeu de données d’entraînement public sur le Hub, la commande lerobot-train, le checkpoint et un taux de réussite sur robot réel sur ≥10 épisodes via lerobot-rollout --policy.path=....
Liste de contrôle PR
Les attentes générales sont décrites dans CONTRIBUTING.md et dans le modèle de PR. En plus de ces éléments, les relecteurs rechercheront :
-
MyPolicyetMyPolicyConfigcouvrent la surface ci-dessus ;__init_subclass__accepte la classe. -
policies/__init__.pyré-exporte la configuration (ce qui enregistre la politique ; la fabrique résout le modèle et le processeur selon la convention de nommage). -
make_my_policy_pre_post_processorsrespecte la convention de nommage. - Les dépendances facultatives sont gérées via l’extra
[project.optional-dependencies]et la gardeTYPE_CHECKING + require_package. -
tests/policies/mis à jour ; artefact de rétrocompatibilité commit et tests spécifiques à la politique. -
src/lerobot/policies/<name>/README.mdest lié symboliquement àdocs/source/policy_<name>_README.md; le fichierdocs/source/<name>.mdxdestiné aux utilisateurs est rédigé et ajouté à_toctree.yml. -
lerobot-train --policy.type my_policy ...s’exécute de bout en bout pendant au moins quelques étapes + enregistre un point de contrôle qui peut être chargé et exécuté parlerobot-evaloulerobot-rollout. -
templates/lerobot_modelcard_template.mdcomporte une entrée de description et un lienpolicy_docspour votre politique. - Le tableau des modèles dans le fichier
README.mdà la racine répertorie votre politique dans la bonne catégorie, avec un lien vers votre page de documentation. - Au moins une évaluation de benchmark reproductible dans le MDX de la politique avec un checkpoint publié (benchmark de simulation, ou jeu de données robot réel + checkpoint).
La façon la plus rapide d’obtenir une PR propre est de copier le répertoire de la politique existante la plus proche de la vôtre, de le renommer et de remplacer le contenu méthode par méthode. N’attendez pas que tout soit poli — ouvrez une PR en brouillon tôt et itérez avec nous ; les relecteurs préfèrent largement donner des retours sur une branche à moitié terminée plutôt que sur une branche entièrement fusionnée.
Exemples et contributions de la communauté
Consultez ces exemples d'implémentations de politiques :
- Politique DiTFlow — Politique Diffusion Transformer avec un objectif de flow-matching. Essayez-la dans cet exemple : Exemple DiTFlow
Merci d’avoir pris le temps d’apporter une nouvelle politique à LeRobot. Chaque architecture qui atterrit dans main — et chaque plugin publié par la communauté — rend la bibliothèque un peu plus utile pour la personne suivante, et un peu plus représentative de l’avenir de l’apprentissage robotique. Nous avons hâte de voir ce que vous allez livrer. 🤗