LeRobot documentation

Praxis-1

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v0.7.0).
Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Praxis-1

praxis est l’adaptateur LeRobot de Praxis-1, le World Action Model à poids ouverts de Runway destiné aux robots. Praxis-1 est un modèle vidéo-action : un backbone de diffusion vidéo causal par blocs (un transformer mixture-of-experts de 27 milliards de paramètres, environ 3 milliards actifs par jeton) débruite les 1,6 s de vidéo suivantes des deux caméras du rig, tandis qu’un expert action, lisant les états cachés du backbone, débruite les 24 commandes cartésiennes correspondantes à 15 Hz. L’autoencodeur vidéo Wan2.2 et l’encodeur texte Qwen3.5-2B sont figés et proviennent de leurs poids publics. Le point de contrôle publié, praxis-1-droid, pilote un rig DROID à un seul bras : un bras Franka avec une caméra extérieure et une caméra de poignet. Un point de contrôle DROID antérieur, non publié, possède la même architecture avec d’autres poids et un autre canevas de caméras ; le config.json du point de contrôle permet de les distinguer (voir « Les points de contrôle »). praxis-1-yam, le point de contrôle bimanuel pour deux bras YAM, est fourni avec la même structure (voir « Le point de contrôle bimanuel YAM »). praxis-1-base, le backbone vidéo et le tronc de l’expert action sans les têtes d’un rig, est le point de départ d’un fine-tuning pour un nouveau rig à un seul bras (voir « Fine-tuning du point de contrôle de base »).

Ressources :

RessourceLienUtilisé pour
Annoncerunway.com/news/praxis-1-is-now-availableAperçu de la méthode
PoidsCollection Praxis-1runwayml/praxis-1-droid-lerobot et les autres points de contrôle convertis
Coderunwayml/praxis-1-ossInférence, simulation et entraînement d'origine
Simulateurpraxis-sim sur PyPILe rig MuJoCo et ses suites d'évaluation
Wan2.2 VAEWan-AI/Wan2.2-TI2V-5BWan2.2_VAE.pth (gelé)
Encodeur de texteQwen/Qwen3.5-2BGelée

Le code du modèle se trouve dans l’arborescence sous lerobot/policies/praxis/praxis_core (vendu depuis le dépôt praxis à la commit 8dad1a4, plus la règle canvas de praxis-1-droid, avec les chemins d’importation réécrits ; les en-têtes de fichiers indiquent les commits en amont). La simulation MuJoCo est le package praxis-sim sur PyPI (développé dans packages/praxis-sim du dépôt praxis, sans torch) ; LeRobot ne propose pas d’environnement pour celui-ci.

Installation

pip install -e ".[praxis,dataset]"   # the policy; `lerobot-eval` and `lerobot-train` also need `dataset`

Les points de contrôle convertis sont sur le Hub : --policy.path=runwayml/praxis-1-droid-lerobot charge le point de contrôle à un seul bras publié (54 Go, téléchargé une seule fois). runwayml/praxis-1-yam-lerobot est le point de contrôle bimanuel. runwayml/praxis-1-base-lerobot est la version de base, qui nécessite d’abord init_heads.py.

La politique s’exécute sur les versions verrouillées par LeRobot 0.7 (0.7.1 au moment de la rédaction : torch 2.15.0 avec CUDA 13.2, transformers 5.22.2, triton 3.9). Les résultats ci-dessous ont été mesurés sur cette pile (avec transformers 5.22.1 et 5.22.2, qui donnent les mêmes commandes) et sur celle de LeRobot 0.6 (torch 2.11.0 avec CUDA 12.8, transformers 5.5.4) ; les commandes sont identiques au bit près à celles du dépôt praxis dans chaque cas.

Noyaux optionnels de la configuration de validation du point de contrôle. flash-attn-4 fournit le backend d’attention flash de la configuration du point de contrôle (flex attention avec le noyau CuTe de FlashAttention-4) ; sans lui, la politique utilise le noyau Triton de flex attention. flash-linear-attention et causal-conv1d sont les noyaux rapides des couches d’attention linéaire de Qwen3.5 ; sans eux, transformers exécute son implémentation PyTorch. Les trois modifient les derniers bits des sorties, pas le modèle :

pip install "flash-attn-4[cu13]==4.0.0b36"        # the CUDA 13 build for torch 2.15; "flash-attn-4==4.0.0b36" on a CUDA 12 torch
pip install "flash-linear-attention==0.6.0"       # 0.5.0 does not run on triton 3.9
CAUSAL_CONV1D_FORCE_BUILD=TRUE MAX_JOBS=16 pip install --no-build-isolation "causal-conv1d==1.6.1"   # compiles against torch; the praxis repository's setup

L'inférence nécessite une GPU avec 80 Go de mémoire (les poids font 54 Go en bfloat16).

Sur les GPU Blackwell de datacenter (B200 et GB200 sont SM100, B300 et GB300 sont SM103 ; Jetson Thor, SM110, a le même FlashAttention-4 forward) :

  • B300 et GB300 nécessitent une compilation de torch pour CUDA 12.9 ou ultérieur, telle que torch 2.15.0+cu132 que LeRobot 0.7 fixe. Les experts MoE appellent torch._grouped_mm, et la compilation de torch 2.11.0+cu128 (LeRobot 0.6) ne dispose d’aucun noyau SM103 pour cela : le premier appel de politique échoue avec RuntimeError: cutlass cannot run, error 7, comme le fait le pipeline du dépôt praxis. Le B200 fonctionne sur les deux piles.
  • FlashAttention-4 n’accepte les masques bloc-sparse que par blocs de 256 jetons (il lève ValueError: Block sparse tensorsforward have block size 128, which must be a multiple of 256). Avec l’arrière-plan flash, la politique construit les masques du squelette par blocs de 256 jetons sur ces GPU, au lieu de la valeur backbone.attention.flex_block_size de 128 du point de contrôle. La taille du bloc ne regroupe que les paires de jetons : la fonction de masque décide toujours de chaque paire. Le dépôt praxis exécute la même chose avec "flex_block_size": 256 dans le config.json du point de contrôle. L’arrière-plan triton conserve la valeur 128 du point de contrôle.

Sur les GPU RTX Blackwell (capacité de calcul 12.x ; le RTX PRO 6000 Blackwell dispose des 96 Go nécessaires aux poids), FlashAttention-4 ne propose pas de forward block-sparse à aucune taille de bloc (avec les blocs de 128 jetons du checkpoint, il lève Block sparsity requires sparse_block_size[1]=128 to match tile_n=64), de sorte que le backend flash ne peut pas s’exécuter dessus. attention_backend="auto" sélectionne triton sur ces GPU, même si flash-attn-4 est installé, et "flash" lève une ValueError lors de la construction de la politique. Le dépôt praxis échoue à son premier appel avec le flash du checkpoint et fonctionne de la même manière avec "backend": "triton" dans le config.json du checkpoint. Les deux backends diffèrent dans les derniers bits, il faut donc comparer les exécutions sur ces GPU avec des exécutions triton ailleurs. Rien d’autre ne change sur un RTX PRO 6000 : les builds torch des deux stacks ci-dessous fournissent des noyaux SM120 (y compris torch._grouped_mm du MoE), et l’inférence avec les paramètres de praxis-1-droid atteint un pic d’environ 62 Go.

Ces règles suivent les propres vérifications de FlashAttention-4. Les résultats de parité ci-dessous ont été mesurés sur un GB300, un B200 et un RTX PRO 6000 Blackwell.

transformers 5.4.0 à 5.5.4 (LeRobot 0.6 ; corrigé dans 5.6.0, et LeRobot 0.7 verrouille 5.22) charge le checkpoint du Hub Qwen/Qwen3.5-2B avec les 320 tenseurs du modèle de langage signalés comme manquants (il renomme les noms stockés model.language_model.* avant la correspondance) et les initialise silencieusement de manière aléatoire. L’encodeur de texte de la politique détecte cela et charge les tenseurs sous leurs noms stockés, avec un avertissement dans le journal. Un encodeur de texte laissé aléatoire produit une politique qui se déplace encore mais réussit rarement (7 épisodes de garde sur 56 lors de notre première exécution).

Entrées et sorties

FonctionnalitéFormeSignification
observation.images.exteriorRGBLa caméra fixe. Toute taille ; le préprocesseur crée la tuile du point de contrôle (224 x 416 avec la règle d’ajustement antialiasé pour praxis-1-droid, 256 x 416 pour le point de contrôle DROID antérieur). Clé définie par exterior_image_key.
observation.images.wristRGBLa caméra de poignet. Clé définie par wrist_image_key.
observation.state(7,)[x, y, z, roll, pitch, yaw, gripper] : la pose de la flange de Franka dans le repère de base du robot (mètres, angles d’Euler extrinsèques x-y-z en radians, convention DROID) et la pince dans [0, 1].
taskstrL'instruction, transmise telle quelle à l'encodeur de texte.
action(7,)Les mêmes sept valeurs : la pose et la pince cibles absolues du prochain tick à 15 Hz.
observation.scene_seed (facultatif)(1,)La graine de scène de l'épisode. La graine du sampleur du bloc k est seed + seed_stride * scene_seed + k.

La politique conserve son propre historique : un tampon circulaire des 25 dernières observations par environnement du lot (le chunk exécuté plus le frame courant) alimente l'appel suivant en tant que contexte, de sorte que n_obs_steps reste à 1 et que select_action doive être appelé à chaque tick de 15 Hz avec l'observation de ce tick. Le premier appel d'une fenêtre de contexte renvoie 21 commandes, chaque appel ultérieur en renvoie 24. Avec n_action_steps inférieur à 24, chaque appel est un démarrage à froid sans contexte, comme dans praxis. drop_queued_actions (par exemple, une nouvelle instruction en cours de déroulement) redémarre également le contexte temporel, car le modèle conditionne sur l'exécution complète du chunk précédent. Un appelant qui exécute des chunks entiers (predict_action_chunk) transmet les frames exécutés depuis son dernier appel sous forme de pile, (B, T, 3, H, W) caméras avec (B, T, 7) états, du plus ancien au plus récent ; le chunk suivant est alors celui d'un déroulement par appel par tick. Un appel de chunk avec un seul frame redémarre le contexte.

Processeurs

Le préprocesseur prépare les entrées de la même manière que le code de données et de service praxis, telles qu’enregistrées, sous forme d’étapes inspectables :

ÉtapeEst-ce
PraxisTileViewsProcessorStepChaque vue de caméra devient sa tuile sur le canevas selon la règle du point de contrôle (video.antialias). praxis-1-droid : recadrage centré sur le ratio natif 720 x 1280, un redimensionnement bilinéaire antialiasé vers 234 x 416 (aucun pour une vue envoyée à cette taille), rognage centré vers 224 x 416. Le point de contrôle DROID antérieur : recadrage centré sur le ratio de la tuile, un simple redimensionnement bilinéaire vers 256 x 416. Niveaux uint8 ; une vue déjà à la taille de la tuile passe telle quelle.
PraxisPoseProcessorStepLa pose DROID [x, y, z, roll, pitch, yaw, gripper] devient la pose praxis8 [x, y, z, qw, qx, qy, qz, gripper] (état, et les actions d'un lot d'entraînement).
PraxisTokenizerProcessorStepL’instruction devient les identifiants de jetons et le masque d’attention de l’encodeur de texte Qwen3.5-2B (observation.language.tokens, .attention_mask), tokenisés de la même manière que l’encodeur de texte praxis. L’encodeur figé et son cache d’incorporations restent dans la politique.
PraxisCommandProcessorStep(postprocessor) La commande de la politique, la pose du câble praxis [x, y, z, rotation 3 x 3] et la pince, deviennent les 7 valeurs DROID, sur le CPU.

Entre eux, la politique voit des tuiles et des lignes pose8 et émet des poses wire, exactement ce que la Session de praxis consomme et produit. La normalisation d’action q99, la normalisation proprio moyenne/écart-type et le décodage d’action relative restent à l’intérieur de la session : ils sont ancrés à la pose au début de chaque chunk exécuté, que la session suit au fil du rollout, et leurs statistiques sont celles du checkpoint (PraxisConfig.model_config["normalization"]) ; les étapes du normaliseur standard sont l’identité. La politique accepte également la pose brute à 7 valeurs, les images brutes de la caméra et la chaîne task, de sorte qu’elle peut être appelée sans le pipeline : les images à la taille de tuile sont prises comme tuiles, les autres tailles passent par la règle de canvas du checkpoint (la règle de fit de praxis-1-droid n’est pas idempotente, donc une tuile ne la traverse jamais deux fois), et une tâche sans jetons est tokenisée par la politique. Les tuiles restent sur l’appareil où le pipeline les place jusqu’à ce que l’autoencodeur les lise, et les commandes reviennent sur le CPU : rien ne traverse la frontière d’appareil deux fois.

Les points de contrôle

Point de contrôleTuilesRedimensionnerÉchantillonneur recommandé (valeurs par défaut de PraxisConfig pour praxis-1-droid)
praxis-1-droid (publié)224 x 416règle anti-aliasing (video.antialias: true)2 pas d'Euler, décalage de flux 12,5, guidage textuel 4,0 sur la vidéo et aucun sur les actions (1,0), 1 bloc de contexte
YAM, praxis-1-yam (bimanuel, publié)256 x 416, trois tuilesRègle de correspondance YAM (video.antialias: true)2 étapes, décalage de flux 1.0, pas de guidage par texte, 1 fragment de contexte (le convertisseur les écrit)
Base, praxis-1-base (sans têtes)défini par init_heads.pydéfini par init_heads.pyle choix du fine-tuning ; le dispositif DROID prend celui de praxis-1-droid
Le point de contrôle DROID antérieur (non publié)256 x 416redimensionnement bilinéaire simple2 étapes, shift 8.0, sans guidage de texte, 3 morceaux de contexte (praxis) ; les résultats ci-dessous ont utilisé shift 1.0, guidage d'action 3.0 et 1 morceau de contexte

Le canevas provient du config.json du point de contrôle (video.tile_height, video.antialias) via le convertisseur ; les paramètres de l’échantillonneur sont des champs de PraxisConfig, de sorte qu’une exécution du point de contrôle DROID précédent transmet les siens (--policy.t_shift=8.0 --policy.cfg_video_scale=1.0 --policy.max_context_chunks=3).

Convertir le point de contrôle

Un point de contrôle praxis est un répertoire contenant config.json, 17 fragments safetensors, model.safetensors.index.json et SHA256SUMS. Convertissez-le en répertoire de politique LeRobot :

python examples/praxis/convert_checkpoint.py \
    --checkpoint /path/to/praxis-1-droid --out outputs/praxis-1-droid --link

Le rig provient de l’embodiment du checkpoint : le checkpoint bimanuel reçoit ses trois clés de caméra (--camera-keys pour les renommer) ainsi que les paramètres de service de son checkpoint. Le checkpoint de base reçoit un répertoire sans processeurs que init_heads.py complète. runwayml/praxis-1-droid-lerobot et les autres checkpoints convertis sont la sortie de ce script.

--link crée des liens symboliques vers les shards au lieu de copier 54 Go. Le répertoire contient le config.json de LeRobot, les shards, l’index et son propre SHA256SUMS (sha256sum -c réussit à l’intérieur) (les politiques LeRobot lisent et écrivent des safetensors shardés dans la disposition transformers ; PraxisPolicy.save_pretrained écrit des shards de 5 Go), ainsi que les deux fichiers de processeur. --vae-id et --text-encoder-id définissent l’origine des parties figées (identifiants Hub par défaut).

Résultats

Les chiffres en boucle fermée ci-dessous proviennent du banc MuJoCo du dépôt praxis (le paquet praxis-sim : Franka + Robotiq 2F-85, caméras 512 x 288, 15 Hz) et de ses suites d'évaluation fixes ; la graine LeRobot d'un épisode est sa graine de scène. L'environnement qui exécute les suites via lerobot-eval ne fait pas partie de LeRobot.

Lors de l’échantillonnage, la politique fixe les paramètres numériques utilisés par praxis (pas de mode benchmark cuDNN, matmuls en float32 pour le routeur MoE, TF32 autorisé dans cuDNN) et restaure les paramètres de l’hôte par la suite, de sorte que --tf32, torch.backends.cudnn.benchmark et la taille du lot ne modifient pas ses sorties.

La suite droid_guard (7 tâches DROID x 8 scènes, 56 épisodes, graine de l'échantillonneur 0) avec le point de contrôle DROID antérieur, non publié (tuiles 256 x 416), aux réglages de l'échantillonneur du dépôt praxis à l'époque (2 pas Euler, décalage de flux 1.0, guidage d'action 3.0, pas de guidage vidéo, 1 bloc de contexte) et son backend d'attention flash, sur une H100 80 Go (environ 10 minutes). Les mêmes épisodes ont été exécutés de cinq manières : l'exécuteur propre au dépôt praxis dans son propre environnement (torch 2.13, transformers 5.2.0, causal-conv1d), le même exécuteur et lerobot-eval sur la pile de LeRobot 0.6 (torch 2.11, transformers 5.5.4), et les deux à nouveau sur la pile verrouillée par LeRobot 0.7 (torch 2.15 avec CUDA 13.2, triton 3.9, flash-linear-attention 0.6.0 ; transformers 5.16.1 à l'époque). La répétition des exécutions torch 2.15 avec transformers 5.22.1 donne des commandes identiques au bit près et les mêmes résultats pour 46/56 épisodes pour l'exécuteur praxis et lerobot-eval, et 5.22.2 (la version verrouillée par LeRobot 0.7.1) donne les mêmes commandes pour praxis-1-droid et le point de contrôle YAM : la version de transformers n'entre pas dans les chiffres.

Tâchepraxis runner, praxis envpraxis runner, torch 2.11lerobot-eval, torch 2.11praxis runner, torch 2.15lerobot-eval, torch 2.15
lift_cube7/87/86/87/88/8
cube_on_plate8/88/88/88/88/8
block_in_bowl8/88/88/88/88/8
block_out_of_bowl7/86/87/87/86/8
move_block7/87/86/87/87/8
stack_block7/88/87/88/86/8
press_button1/80/83/81/83/8
Total45/56 80 %44/56 79 %45/56 80 %46/56 82 %46/56 82 %

Appariés sur les 56 épisodes (praxis_sim.compare), lerobot-eval moins la référence :

ComparaisonDifférenceIntervalle de confiance par bootstrap à 95 %Gagné / perdup de McNemar
torch 2.11 : lerobot-eval vs runner praxis, env praxis+0.0 %[-10.7 %, +10.7 %]5 / 51.00
torch 2.11 : lerobot-eval vs runner praxis, même pile+1.8 %[-8.9 %, +12.5 %]6 / 51.00
torch 2.15 : lerobot-eval vs runner praxis, env praxis+1.8 %[-10.7 %, +14.3 %]6 / 51.00
torch 2.15 : lerobot-eval vs runner praxis, même pile+0.0 %[-10.7 %, +10.7 %]4 / 41.00

Chaque intervalle inclut zéro : la politique et l’environnement LeRobot reproduisent les résultats du dépôt praxis, et ni la pile logicielle (noyaux, version de transformers) ni les modifications du harnais ne les altèrent au-delà du bruit d’un épisode à l’autre. Le lanceur praxis lui-même varie de la même amplitude entre les piles (de 44 à 46 sur 56). press_button est difficile pour ce point de contrôle dans toutes les configurations.

Avec des entrées identiques, la politique est égale au niveau du bit par bit au pipeline praxis sur les deux piles (voir ci-dessous) ; entre les deux piles, les commandes pour des entrées identiques diffèrent d’au plus 0,04 (flash) et 0,02 (triton), ce qui déplace les épisodes individuels.

praxis-1-droid , le point de contrôle publié

Les mêmes 56 épisodes avec praxis-1-droid (tuiles antialiasées 224 x 416) à ses paramètres recommandés (2 pas Euler, flow shift 12.5, video guidance 4.0, pas d’action guidance, 1 context chunk) et le backend flash, sur une H100 80 GB : le runner du dépôt praxis (son main avec le sampler actuel, dans l’environnement praxis : torch 2.13, transformers 5.2.0), lerobot-eval sur la pile de LeRobot 0.6 (torch 2.11, transformers 5.5.4), et à la fois le runner et lerobot-eval sur la pile verrouillée par LeRobot 0.7 (torch 2.15 avec CUDA 13.2, transformers 5.22.1).

Tâchepraxis runner, praxis envlerobot-eval, torch 2.11praxis runner, torch 2.15lerobot-eval, torch 2.15
lift_cube7/88/88/88/8
cube_on_plate8/87/88/88/8
block_in_bowl8/88/88/88/8
block_out_of_bowl8/88/87/88/8
move_block7/88/88/88/8
stack_block8/87/88/86/8
press_button8/88/87/88/8
Total54/56 96 %54/56 96 %54/56 96 %54/56 96 %

Appariés sur les 56 épisodes, lerobot-eval moins le runner praxis est à +0,0 point sur les deux stacks (intervalle bootstrap à 95 % [-7,1 %, +7,1 %], 2 gagnés / 2 perdus, p de McNemar 1,00 chacun). Les épisodes qui basculent ne sont pas les mêmes sur les deux stacks : les noyaux de torch 2.15 déplacent les commandes de jusqu'à 0,02, et quelques épisodes limitrophes suivent. praxis-1-droid avec ses réglages résout press_button, ce que le checkpoint DROID antérieur avec ses réglages échouait à faire dans la plupart des cas.

Parité avec le dépôt praxis

  • Sur des observations identiques dans un même processus, la politique LeRobot et la Session praxis produisent des embeddings de texte, des latents VAE, des données proprioceptives, des actions échantillonnées et des commandes strictement identiques au bit près (mêmes poids chargés). tests/policies/praxis/test_praxis.py répète cette vérification sur un petit modèle aléatoire sur CPU.
  • Entre les processus, l’exécution du pipeline praxis via le package original praxis et la politique LeRobot produit des commandes strictement identiques au niveau des bits sur le checkpoint réel, sur les backends flash et triton (les deux backends diffèrent l’un de l’autre d’au plus 0,055 dans les commandes), sur la pile de LeRobot 0.6 (torch 2.11.0+cu128, transformers 5.5.4, flash-attn-4 4.0.0b36) et sur la pile verrouillée par LeRobot 0.7 (torch 2.15.0+cu132, transformers 5.22.1 et 5.22.2, triton 3.9, flash-attn-4 4.0.0b36). La référence elle-même varie entre les deux piles (jusqu’à 0,019 sur flash, 0,015 sur triton) ; la politique la suit bit pour bit sur chacune.
  • praxis-1-droid : la vérification inter-processus par rapport au pipeline praxis du dépôt praxis sur la branche main (avec son échantillonneur actuel) est identique au bit près sur les deux backends et les deux stacks, pour huit façons d’appeler la politique : via ses pipelines de processeurs enregistrés (comme le fait lerobot-eval), sur des images brutes et la pose à 7 valeurs sans les pipelines, avec deux environnements dans un même lot (instructions et graines de scène différentes), via predict_action_chunk (un démarrage à froid, puis le chunk suivant en fournissant les images exécutées comme pile), avec n_action_steps=12 (un chunk toutes les 12 commandes, conditionné sur les images exécutées), avec le guidage d’action activé (cfg_scale=3), et sur 70 commandes avec trois chunks de contexte (profondeur de 0 à 3). La vérification tient avec les drapeaux TF32 et cuDNN benchmark par défaut de lerobot-eval : la politique fixe les valeurs numériques de praxis pendant l’échantillonnage et rend les drapeaux de l’hôte. L’étape de tuilage calcule le canevas sur le CPU quelle que soit la carte des images : le redimensionnement antialiasé CUDA arrondit d’un niveau différent sur environ un tiers des pixels, ce qui déplaçait les commandes d’au plus 0,02 avant que l’étape ne le fasse.
  • Entraînement : sur les poids publiés, PraxisPolicy.forward et praxis.train.objective du dépôt praxis produisent la même perte (et les mêmes termes vidéo et action) bit pour bit pour la même fenêtre et les mêmes tirages aléatoires, ainsi que les mêmes gradients sur les 200 tenseurs de l'expert d'action. La métrique d'équilibrage MoE rapportée varie d'un ou deux ulps float32 entre les exécutions des deux côtés (une réduction non déterministe dans la statistique d'équilibrage ; elle entre dans la perte avec un poids de 1e-4).
  • GB300 (SM103, hôte aarch64) : sur la pile, LeRobot 0.7 verrouille (torch 2.15.0+cu132, cuDNN 9.26, transformers 5.22.1, triton 3.9, flash-attn-4 4.0.0b36, flash-linear-attention 0.6.0), la vérification inter-processus par rapport au dépôt praxis sur main est identique au bit près sur les deux backends (flash avec des blocs de masque de 256 jetons des deux côtés) : pour praxis-1-droid de sept manières d’appeler la politique (les pipelines enregistrés sous les drapeaux de lerobot-eval, images brutes 720 x 1280, deux environnements dans un seul lot, predict_action_chunk avec des piles d’images, n_action_steps=12, cfg_scale=3, et 94 commandes avec trois blocs de contexte), pour praxis-1-yam via les pipelines, sur des images brutes et avec deux environnements, et pour le dispositif DROID reconstruit à partir de praxis-1-base. forward donne la perte de l’objectif praxis et les gradients de tous les tenseurs d’experts d’action (200) au bit près sur les poids publiés.
  • RTX PRO 6000 Blackwell (SM120, 96 GB, hôte x86_64), triton des deux côtés : sur la pile de LeRobot 0.6 (torch 2.11.0+cu128, cuDNN 9.19, transformers 5.5.4, triton 3.6, flash-linear-attention 0.5.0) et sur la pile verrouillée par LeRobot 0.7 (torch 2.15.0+cu132, cuDNN 9.26, transformers 5.22.1, triton 3.9, flash-linear-attention 0.6.0 et causal-conv1d 1.6.1, afin que Qwen3.5 exécute leurs noyaux), les deux avec flash-attn-4 4.0.0b36 installé, la vérification inter-processus par rapport au main du dépôt praxis est bit à bit identique de la même manière que sur le GB300 : praxis-1-droid sur les sept (463 commandes, profondeurs de contexte de 0 à 3), praxis-1-yam sur les trois, et le dispositif DROID reconstruit à partir de praxis-1-base (ses six tenseurs de tête et sa configuration sont égaux à ceux de init_heads du dépôt praxis). forward fournit la perte de l'objectif praxis et les gradients de tous les tenseurs d'experts d'action (200) bit à bit sur les deux piles, avec le backbone gelé des deux côtés (ses 54 Go de gradients ne tiennent pas à côté des poids). Sur la pile de LeRobot 0.6, le côté praxis a également besoin de la correction Qwen3.5 de l'avertissement ci-dessus : praxis-1 fixe transformers 5.2.0, et sous 5.5.4 son encodeur de texte reste aléatoire. Boucle fermée sur droid_guard avec praxis-1-droid sur la pile de LeRobot 0.6 : lerobot-eval 53/56 (94,6 %), le lanceur praxis 54/56 (96,4 %, comme sur le H100 et le GB300) ; apparié, -1,8 points, intervalle à 95 % [-8,9, +5,4], 2 gagnés / 3 perdus, p de McNemar 1,00. Un fine-tuning LoRA via lerobot-train (r 16, alpha 32, cibles par défaut, une fenêtre par étape) s'exécute sur le GPU unique : 5,5 M de paramètres entraînables, 1,1 s par étape, 62 Go.
  • B200 (SM100, hôte x86_64) : sur la pile de LeRobot 0.6 (torch 2.11.0+cu128, cuDNN 9.19, transformers 5.5.4, triton 3.6) et sur la pile verrouillée par LeRobot 0.7 (torch 2.15.0+cu132, cuDNN 9.26, transformers 5.22.1, triton 3.9, flash-linear-attention 0.6.0), les deux avec flash-attn-4 4.0.0b36, la vérification inter-processus par rapport au dépôt praxis main est identique au bit près sur les deux backends (flash avec des blocs de masque de 256 jetons des deux côtés) de la même manière que sur le GB300 : praxis-1-droid sur les sept (463 commandes par backend et par pile, profondeurs de contexte de 0 à 3), praxis-1-yam sur les trois, et le dispositif DROID reconstruit à partir de praxis-1-base (ses six tenseurs de tête et sa configuration sont égaux à ceux de init_heads du dépôt praxis). Les deux backends diffèrent l'un de l'autre d'au plus 0,19 dans les commandes. forward fournit la perte de l'objectif praxis, ses termes vidéo et action ainsi que les gradients de tous les 200 tenseurs d'experts d'action, au bit près, sur les deux backends et les deux piles, le backbone entraîné. Un fine-tuning LoRA via lerobot-train s'exécute sur le GPU unique (1,2 s par étape, 62 Go), et l'adaptateur ainsi que son point de contrôle fusionné se chargent dans lerobot-eval.
  • Boucle fermée sur le B200 (droid_guard, praxis-1-droid, flash) : le runner praxis obtient 54/56 sur la pile torch 2.15 et 55/56 sur torch 2.11, lerobot-eval 53/56 et 51/56 (apparié : -1,8 points, intervalle à 95 % [-8,9, +5,4] ; et -7,1 points, [-14,3, -1,8], 0 victoire / 4 défaites, p de McNemar 0,125). Chaque harness se répète épisode par épisode, donc l'écart n'est pas du bruit entre les exécutions, et ce n'est pas non plus la politique : lerobot-eval transmet l'état et les commandes sous forme de 7 valeurs DROID en float32 (PraxisSimGymEnv, les étapes du processeur de pose et de commande), tandis que le runner praxis passe les poses et les commandes de la session telles quelles. Le aller-retour déplace une pose d'environ 1e-7, et sur un épisode la différence s'amplifie en une autre trajectoire. Le runner praxis, avec son état et ses commandes envoyés via la même conversion float32, donne le résultat de lerobot-eval sur les 56 épisodes, sur les deux piles.
  • Face à l’environnement propre du dépôt praxis (torch 2.13.0+cu130, transformers 5.2.0, causal-conv1d), les sorties ne sont pas égales au bit près : les noyaux diffèrent, l’embedding de Qwen3.5 diffère jusqu’à 0.125 en bfloat16, et les derniers bits des actions suivent. La vérification de dépôt à dépôt est donc l’évaluation en boucle fermée appariée ci-dessus.
  • La simulation est rendue avec un échantillonnage multiple 4x, et quelques pixels par image diffèrent entre les rendus de la même scène, de sorte que deux épisodes en boucle fermée de la même politique divergent après le premier bloc, même avec des poids identiques. Les épisodes appariés sont comparés statistiquement, et non image par image.

Le point de contrôle bimanuel YAM

praxis-1-yam ("embodiment": "bimanual", runwayml/praxis-1-yam-lerobot) pilote deux bras YAM avec une caméra supérieure et deux caméras de poignet (vues ZED 16:10, 1920 x 1200). Le même modèle le fait tourner ; le point de contrôle diffère par les largeurs des têtes d'expert (20 valeurs d'action : [xyz, rot6d, gripper] par bras), le canevas à trois tuiles et la normalisation. L'adaptateur LeRobot suit le praxis/bimanual.py du dépôt praxis (vendu en tant que praxis_core/bimanual.py) :

CléFormeSignification
observation.images.top_left, observation.images.wrist_left, observation.images.wrist_rightRGBLes trois vues dans l’ordre du canevas (camera_keys). Toute taille 16:10 ; le processeur génère les tuiles de 256 x 416 selon la règle d’ajustement YAM (les vues s’insèrent ensemble dans une boîte de 416 px, puis un rognage de 32 px est appliqué).
observation.state(14,)[x, y, z, roll, pitch, yaw, gripper] du bras gauche, puis du bras droit, dans le repère monde du rig (pose_frame: rig, sans rotation de bride). 16 valeurs (pose8 par bras) passent également.
action(14,)Les mêmes 14 valeurs : la cible absolue de chaque bras pour le prochain tick de 15 Hz. select_action émet 26 valeurs (la pose du câble et la pince de chaque bras) ; l'étape de commande les convertit.

Les paramètres de service du checkpoint (le fichier configs/serve_yam.yaml du dépôt praxis : 2 pas d’Euler, décalage de flux 1.0, pas de guidage textuel, 1 bloc de contexte, le rig bimanuel n’a pas de mode de contrôle) sont ceux que le convertisseur écrit dans la configuration, de sorte que --policy.path=runwayml/praxis-1-yam-lerobot les exécute. Les prompts sont transmis tels quels à l’encodeur textuel ; le checkpoint a été entraîné sur des légendes telles que « Pick up the pen from the center of the table, then place the pen in the bag », sans point final.

Parité : sur le point de contrôle réel, la politique LeRobot et la BimanualServeSession du pipeline praxis produisent des commandes strictement identiques bit à bit pour les mêmes trois vues, poses et instruction (22 ticks : le chunk de démarrage à froid et la première commande du chunk de profondeur 1) via les pipelines sauvegardés, sur des images brutes sans eux, et avec deux environnements aux instructions différentes dans un même lot. Le test sur le petit modèle répète la vérification sur le CPU par rapport à la Session praxis.

Limites : il n’y a pas de simulation du rig YAM, donc pas de suite en boucle fermée ; forward (fine-tuning) est limité à un seul bras, car le dépôt praxis ne propose pas encore de recette d’entraînement bimanuel.

Entraînement

lerobot-train ajuste finement la politique avec l’objectif du dépôt praxis (praxis/train) : la perte conjointe de flow-matching de la vidéo et des actions, le dropout sur le texte et la proprioception, le terme d’équilibrage des séquences MoE et la mise à jour du biais de routage après chaque étape de l’optimiseur. À fenêtre et tirages aléatoires identiques, PraxisPolicy.forward est bit à bit égal à l’objectif de l’entraîneur praxis sur le point de contrôle publié. Une fenêtre est constituée de 93 images consécutives (paquets de 21, 24, 24, 24) des deux caméras, avec la pose observée et commandée de la flange pour chaque image et l’instruction ; la politique la déclare via observation_delta_indices / action_delta_indices (93 images) et maintient les fenêtres à l’intérieur des épisodes grâce à drop_n_last_frames (93, de sorte que l’échantillonneur tire les débuts de fenêtre de la recette). Les tuiles des caméras suivent la règle de canevas du point de contrôle (video.antialias), comme dans le module de données praxis. forward s’exécute avec les arithmétiques praxis, quel que soit le lanceur : pas d’autocast (les scores du routeur MoE restent en float32), pas de TF32 dans les produits matriciels float32. Le nombre de fenêtres d’entraînement vues est un tampon de la politique (train_counter) : il initialise les tirages, voyage avec le point de contrôle et l’adaptateur, et une exécution reprise continue la séquence ; une évaluation sur un jeu retenu ne l’avance pas.

Données

La recette s’entraîne sur DROID. lerobot/droid_1.0.1 contient la pose cartésienne sous forme de six plus une colonnes ; les colonnes 7-D observation.state.cartesian et action.cartesian de PR #11 les pré-assemblent (utilisez --dataset.revision=refs/pr/11 jusqu’à sa fusion). Les colonnes observation.state et action du jeu de données sont en espace articulaire ; la carte de renommage place les colonnes cartésiennes à leur emplacement (une clé renommée remplace une clé existante) et les deux caméras sous les clés que la politique lit :

RENAME='{"observation.state.cartesian":"observation.state","action.cartesian":"action",
"observation.images.exterior_1_left":"observation.images.exterior","observation.images.wrist_left":"observation.images.wrist"}'

lerobot-train applique la carte au lot brut puis à nouveau dans le pipeline du processeur, il ne doit donc pas échanger les clés (la correspondance de observation.state vers un autre nom serait annulée).

Environ la moitié des épisodes de DROID ne comportent aucune instruction et certains sont plus courts qu'une fenêtre ; la recette les exclut. examples/praxis/select_droid_episodes.py écrit la liste pour --dataset.episodes :

python examples/praxis/select_droid_episodes.py --repo-id lerobot/droid_1.0.1 \
    --revision refs/pr/11 --out droid_episodes.json

La copie du Hub stocke des vidéos de 180x320 ; le checkpoint a été entraîné sur les enregistrements DROID de 1280x720. Les tuiles sont des agrandissements avec moins de détails sur la copie du Hub, ce qui est suffisant pour un test de fumée. Pour un fine-tuning fidèle, créez une copie locale avec les vidéos brutes comme décrit dans praxis/train/README.md du dépôt praxis, et pointez --dataset.root vers celle-ci.

Ajustement complet

Les poids sont en bfloat16 ; le préréglage d’optimiseur praxis_adamw est AdamW avec arrondi stochastique (un AdamW classique arrondit les mises à jour de 3e-6 à zéro), le planificateur est un échauffement linéaire et un décroissement cosinus jusqu’à 3e-7, et chaque bloc transformer recalcule son passage avant lors du passage arrière (activation_checkpointing, activé par défaut). Les poids, les gradients et les moments de l’optimiseur occupent 8 octets par paramètre, soit 218 Go pour le modèle de 27,2 Md, répartis sur les GPU par FSDP : un fine-tuning complet nécessite au moins 4 x 80 Go. La recette praxis est de 96 fenêtres par étape pendant 1 200 étapes ; sur 8 GPU, cela correspond à --batch_size=1 --accelerator.gradient_accumulation.steps=12. lerobot-train compte les micro-lots (une étape de boucle est un lot sur chaque GPU) et fait avancer le planificateur par micro-lot, de sorte que les 1 200 mises à jour sont --steps=14400, avec l’échauffement et le décroissement en micro-étapes.

torchrun --nproc-per-node=8 $(which lerobot-train) \
    --dataset.repo_id=lerobot/droid_1.0.1 --dataset.revision=refs/pr/11 \
    --dataset.episodes="$(cat droid_episodes.json)" --rename_map="$RENAME" \
    --policy.path=runwayml/praxis-1-droid-lerobot --policy.device=cuda --policy.push_to_hub=false \
    --parallelism.dp_shard=8 --accelerator.fsdp.mixed_precision_policy.reduce_dtype=float32 \
    --batch_size=1 --accelerator.gradient_accumulation.steps=12 --steps=14400 \
    --policy.scheduler_warmup_steps=600 --policy.scheduler_decay_steps=14400 \
    --num_workers=8 --save_freq=100 --output_dir=outputs/train/praxis_droid

Une sauvegarde contient le modèle sharded de 54 Go (pretrained_model/, chargeable par PraxisPolicy.from_pretrained et lerobot-eval) ainsi qu’environ 108 Go d’état de l’optimiseur. PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True évite la fragmentation de l’allocateur, comme dans le formateur praxis.

Mesuré sur 8 x H100 80 GB avec praxis-1-droid, une fenêtre par GPU et --dataset.episodes de 256 épisodes DROID (les vidéos du Hub), 1 000 étapes d’optimiseur : 3,7 s par étape après la première (qui compile les noyaux), 48,5 Go de mémoire GPU par rang, le chargeur de données tenant le rythme avec 8 workers par rang. La perte passe de 0,216 sur les cent premières étapes à 0,190 sur les cent dernières (vidéo 0,191 à 0,171, action 0,025 à 0,019). Un checkpoint sauvegarde le modèle sharded et l’état de l’optimiseur en environ deux minutes via FSDP. Le checkpoint fine-tuné obtient 50 sur 56 sur droid_guard contre 54 pour le modèle de base : les vidéos du Hub sont des upscaling 180x320, donc un fine-tuning sur elles éloigne la politique des enregistrements 1280x720 sur lesquels elle a été entraînée.

LoRA

Adaptateurs via PEFT (pip install -e ".[peft]"). Les cibles par défaut sont le groupe expert de praxis, ainsi que les projections d'attention et de MLP de l'expert d'action (PraxisPolicy.PEFT_TARGETS) ; les projections de sortie de l'attention du backbone constituent le deuxième groupe, transmis avec --peft.target_modules. Les poids des experts MoE ne sont pas des couches linéaires et n'acceptent pas d'adaptateurs. Avec les poids de base gelés, une exécution LoRA tient sur moins de GPU :

torchrun --nproc-per-node=4 $(which lerobot-train) ... --parallelism.dp_shard=4 \
    --peft.r=16 --peft.lora_alpha=32 --output_dir=outputs/train/praxis_lora

Laisser --peft.full_training_modules non défini : la valeur par défaut de modules_to_save de la politique est le compteur d’entraînement, que l’adaptateur doit porter (la configuration refuse un adaptateur sans lui) ; les seuls autres modules qu’elle accepte sont les trois modules de tête du rig, pour un fine-tune depuis le checkpoint de base (ci-dessous). Les biais de routage MoE appartiennent à la base et restent fixes sous PEFT. Mesuré avec r 16, alpha 32 et les cibles par défaut sur 4 x H100 (--parallelism.dp_shard=4) : 5,5 M de paramètres entraînables, 2,3 s par pas avec une fenêtre par GPU, 30 Go de mémoire GPU par rang, un adaptateur de 22 Mo ; --parallelism.dp_replicate avec dp_shard (HSDP) fonctionne également. La perte totale reste proche de 0,22 sur 500 pas car le backbone gelé gère le terme vidéo ; le terme action passe de 0,026 à 0,022. L’adaptateur de cette exécution obtient 52 sur 56 sur droid_guard, chargé par-dessus la base par lerobot-eval.

Le point de contrôle contient l'adaptateur (adapter_config.json, adapter_model.safetensors) ; lerobot-eval le charge par-dessus le modèle de base via --policy.path. Pour obtenir un point de contrôle standard pour la simulation, le serveur et from_pretrained, fusionnez l'adaptateur dans les poids :

python examples/praxis/merge_praxis_adapter.py --base runwayml/praxis-1-droid-lerobot \
    --adapter outputs/train/praxis_lora/checkpoints/last/pretrained_model --out outputs/praxis-1-droid-lora

Différences par rapport au formateur praxis

L’objectif, la recette de fenêtre, l’optimiseur et le planning sont ceux de praxis. Les données proviennent de LeRobotDataset au lieu du lecteur de fichiers praxis, les tirages aléatoires sont initialisés à partir de la graine LeRobot, du nombre d’appels, du rang et de la position dans le lot (les pertes sont donc reproductibles, mais ne correspondent pas pas à pas à celles d’une exécution praxis), et les points de contrôle sont des points de contrôle LeRobot. Non portés : le mode surapprentissage avec tirages fixes et no_sync.

Ajuster finement le point de contrôle de base

praxis-1-base ("embodiment": "base", runwayml/praxis-1-base-lerobot) est le backbone vidéo et le tronc de l’expert en actions à la fin du pré-entraînement, sans les six tenseurs de tête d’un rig (action_encoder.w1, embed_proprio, head, poids et biais) et sans état d’optimiseur. Il ne se charge pas comme une politique ; deux scripts le transforment en point de départ d’un fine-tuning pour votre rig à un seul bras, la forme LeRobot des scripts compute_norm_stats.py et init_heads.py du dépôt praxis :

# the normalization statistics of your data: 1 % / 99 % quantiles of the relative actions and the
# mean and std of the proprio over random training windows (no video is read)
python examples/praxis/compute_norm_stats.py --repo-id lerobot/droid_1.0.1 --revision refs/pr/11 \
    --state-key observation.state.cartesian --action-key action.cartesian \
    --episodes droid_episodes.json --n-windows 2000 --out stats.json
# the base plus six new head tensors (`--seed`) and the rig fields: a policy directory that loads
python examples/praxis/init_heads.py --base runwayml/praxis-1-base-lerobot --out outputs/praxis-1-myrig \
    --stats stats.json --tile-height 224 --tile-width 416 --antialias --link
# then the full fine-tune command above with --policy.path=outputs/praxis-1-myrig, or LoRA with the
# six new head tensors trained in full next to the adapters:
torchrun --nproc-per-node=4 $(which lerobot-train) ... --policy.path=outputs/praxis-1-myrig \
    --parallelism.dp_shard=4 --peft.r=16 --peft.lora_alpha=32 \
    --peft.full_training_modules='["train_counter","action_expert.action_encoder.w1","action_expert.embed_proprio","action_expert.head"]'

Les colonnes state et action contiennent la pose DROID et la pince [x, y, z, roll, pitch, yaw, gripper] par image (les colonnes propres au jeu de données, ou une carte de renommage). --stats-from <single-arm checkpoint> copie à la place un bloc de normalisation existant (le rig DROID). Le rig DROID reconstruit à partir de la base et des statistiques du checkpoint publié a exactement la configuration de praxis-1-droid, et les tenseurs de la tête sont ceux du fichier init_heads.py du dépôt praxis pour la même graine, bit pour bit. Sur le sous-ensemble DROID des exécutions ci-dessus, compute_norm_stats.py reproduit les statistiques du checkpoint à 0,01 m près sur les quantiles xyz, avec la pince fixée à 0 et 1. Le fine-tuning de base du dépôt praxis (configs/finetune_base.yaml, un jour sur 8 GPU) atteint 62,5 % sur droid_guard à partir de cette base, contre 91 % pour praxis-1-droid entraîné avec environ 40 fois plus de fenêtres : la base est un point de départ didactique pour un nouveau rig, et non un remplacement du checkpoint publié. Le fine-tuning complet entraîne chaque poids à optimizer_lr ; sous LoRA, les adaptateurs et les six tenseurs de la tête s’entraînent (PraxisPolicy.PEFT_HEAD_MODULES sont les seuls modules, outre le compteur, que full_training_modules accepte) et le reste du tronc reste figé ; les étapes de warm-up uniquement sur la tête et l’échelle du taux d’apprentissage de la tête de la recette praxis ne sont pas portées.

Limites connues

  • Deux configurations : le point de contrôle DROID à un seul bras (embodiment: single_arm) et la configuration YAM bimanuelle (bimanual). L’ajustement fin et la simulation sont à un seul bras.
  • Praxis génère des cibles de pose cartésiennes pour un bras équipé d’un contrôleur d’impédance ; les classes de robot de LeRobot n’incluent pas de Franka, donc l’utilisation sur un robot réel passe par un client qui exécute les cibles cartésiennes.
  • Un lot select_action exécute ses environnements séquentiellement (un appel au modèle par environnement).
  • Le service à distance n’est pas pris en charge. L’exécuteur de chunks par défaut (lerobot/inference/policy_runner.py) refuse une politique qui conditionne sur des observations passées, et Praxis conditionne sur les images des chunks exécutés (current_observation_only=False dans chunk_inference_spec) ; l’étape de tuilage redimensionne également les images de la caméra à la taille de la toile du point de contrôle, de sorte qu’elles ne correspondraient pas aux formes vérifiées par validate_chunk_input_features. Exécutez la politique en processus (lerobot-eval, un client robot).
Update on GitHub