LeRobot documentation

Flux de jeu de données d'entraînement

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Flux de jeu de données d'entraînement

Le streaming de jeu de données à l'entraînement permet à lerobot-train de consommer un LeRobotDataset v3 sans d'abord télécharger sa charge complète de Parquet et de vidéo. Activez-le via l'interrupteur public existant :

lerobot-train \
  --dataset.repo_id=OWNER/DATASET \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

Cette fonctionnalité est indépendante de --dataset.streaming_encoding=true. streaming_encoding contrôle la manière dont les vidéos sont écrites pendant l’enregistrement ; dataset.streaming contrôle la manière dont un jeu de données existant est lu pendant l’entraînement. Les encodeurs d’enregistrement et de déroulement ne sont pas utilisés par ce chemin d’entraînement.

Comment une époque est lue

Chaque rang distribué possède un ensemble déterministe et équilibré par image d’épisodes complets. Un pool de couverture exacte par travailleur de DataLoader mélange la part de ces épisodes du travailleur tout en visitant chaque image sélectionnée une fois par époque de couverture. Les colonnes Parquet et les plages d’octets des MP4 compressés sont préchargées depuis la même frontière d’admission consciente des octets. Les fenêtres d’historique temporel et futures sont résolues à l’intérieur de l’épisode complet, y compris les mêmes masques de bourrage aux limites que pour le chargement de type map.

Le comportement par défaut de LeRobotDataset au style map reste inchangé lorsque --dataset.streaming=false.

Workers, shards et concurrence

Chaque rang possède un fragment équilibré en termes de frames d’épisodes complets. Au sein d’un rang, --num_workers définit le nombre de processus workers du DataLoader, comme pour les jeux de données de type map. Chaque worker possède une partie disjointe et équilibrée en termes de frames des épisodes du rang, avec son propre pool de couverture exacte, sa propre part du budget d’octets et ses propres pools de threads :

StageConcurrenceConfiguration
Répartition par rangUn shard d'épisode complet disjoint par rangTaille du monde distribué
Travailleurs du DataLoaderUne partie disjointe du shard de rang par travailleur--num_workers
Récupération d'épisodeLignes Parquet et plages d'octets MP4, en parallèlemax_num_shards, défini à partir de --num_workers
Assemblage et décodage d'échantillonsFenêtres temporelles et images vidéo, en parallèle--dataset.streaming_decode_threads
Tampon d'échantillons ordonnéÉchantillons décodés conservés en tête, dans l'ordre du planificateur--dataset.streaming_decoded_queue_size

Pourquoi plusieurs workers. L’assemblage des échantillons et la transmission des lots exécutent du code Python, et un processus exécute du code Python sur un seul thread à la fois (le GIL). Avec un seul worker, les threads de décodage d’un rang s’attendent mutuellement, si bien que le rang n’utilise qu’environ 2 cœurs CPU. Chaque worker est un processus distinct, de sorte que N workers assemblent et décodent les échantillons en parallèle.

Taille du pool et diversité des lots. streaming_episode_pool_size définit le pool de chaque travailleur. Si vous ne le définissez pas, le rank mélange 32 épisodes et chacun des N travailleurs en reçoit 32/N. Chaque travailleur précharge également streaming_prefetch_episodes épisodes et constitue un processus Python distinct, de sorte que plus il y a de travailleurs, plus la mémoire utilisée est importante : sur un jeu de données avec 3 caméras, 4 travailleurs utilisaient environ 8 Go au total, contre environ 3 Go avec un seul travailleur. Le DataLoader extrait chaque lot d'un seul travailleur, de sorte qu'un lot mélange les épisodes du pool d'un seul travailleur. Un pool plus grand mélange davantage d'épisodes dans chaque lot, mais utilise plus de mémoire et récupère plus de données avant le premier échantillon.

Limites. Le budget d’octets (streaming_byte_budget_gb) et la taille explicite du cache du décodeur vidéo (video_decoder_cache_size) restent par rang : chaque travailleur reçoit 1/N de ces valeurs. Un travailleur a besoin d’au moins un épisode, donc lerobot-train utilise au plus autant de travailleurs que le rang ne possède d’épisodes. --num_workers=0 conserve le jeu de données dans le processus d’entraînement. Dans ce cas, le thread d’entraînement applique les transformations d’image, de sorte que la même valeur de --seed produit les mêmes transformations.

MP4 sidecars et la première exécution

Le streaming vidéo utilise un index MP4 en fichier compagnon. L’initialisation du jeu de données vérifie d’abord le cache local indexé par révision, puis recherche un fichier compagnon publié et valide. Si aucun n’est disponible, LeRobot construit le fichier compagnon localement en maintenant un verrou de processus et l’installe de manière atomique. Une construction échouée ou interrompue ne remplace pas le fichier valide précédent. Les autres processus ayant besoin du même fichier compagnon attendent le verrou pendant au plus --dataset.streaming_sidecar_lock_timeout_s (30 minutes par défaut). Augmentez cette valeur pour les grands jeux de données dont l’indexation prend plus de temps.

Les lectures de charge utile du Hub sont verrouillées sur le commit de l’instantané de métadonnées. Les index de Bucket sont vérifiés par rapport à un nouvel inventaire d’objets, y compris les hachages de contenu, de sorte que le remplacement d’une vidéo au même chemin invalide son sidecar, même si la taille du fichier est inchangée. Cet inventaire ajoute du travail au démarrage, mais ne télécharge pas les charges utiles vidéo. Conservez les métadonnées et les charges utiles inchangées pendant une exécution. Les systèmes de fichiers distants génériques sans identités d’objets stables reconstruisent leur index à chaque ouverture. Les sidecars de Bucket existants sans empreintes d’objets sont reconstruits localement une fois ; la publication du sidecar mis à jour reste une action explicite du mainteneur.

L’entraînement est en lecture seule : il ne téléverse jamais de fichier annexe et ne modifie pas le dépôt de jeu de données. Sur un cluster avec des caches locaux par nœud, le premier travail peut construire une fois par nœud. Un cache LeRobot partagé évite cette duplication.

La construction du sidecar écrit chaque index de source terminé dans une archive temporaire. Elle conserve au plus le nombre de résultats d’analyse configuré. La mémoire des tableaux dépend des sources concurrentes les plus volumineuses ; les métadonnées de l’archive continuent de croître avec le nombre de sources. Utilisez --workers pour définir la concurrence d’analyse lors de l’exécution de lerobot-build-mp4-sidecar. Une construction échouée conserve le sidecar précédent.

Les archives NPZ publiées au schéma-3 conservent des tableaux d’entiers canoniques int64. La première ouverture convertit l’archive en un index en lecture seule, mappé en mémoire, sous $HF_LEROBOT_HOME/streaming-indexes. L’index dérivé utilise les types d’entiers exacts les plus petits. Il stocke une durée constante, un décalage de composition, une taille ou un décalage d’octet une seule fois. Les vues de tableaux en lecture seule conservent le nombre logique d’échantillons. Les horodatages de présentation conservent des valeurs exactes float64. Les tableaux d’échantillons synchronisés conservent un stockage dense.

L’index dérivé utilise une génération de cache distincte. Les lecteurs plus anciens conservent leur format de cache existant. La première ouverture avec le nouveau lecteur peut convertir l’archive une seule fois ; les ouvertures ultérieures en réutilisent le résultat. La conversion utilise un verrou de processus et un remplacement atomique. Les rangs qui partagent le cache réutilisent les mêmes pages basées sur des fichiers. Chaque rang ne construit des enregistrements que pour les fichiers sources utilisés par ses épisodes. Les générations de cache anciennes et nouvelles peuvent nécessiter un espace disque supplémentaire. Le remplacement d’un fichier annexe crée une nouvelle génération immuable sans invalider les lecteurs actifs.

Cela ne rend pas toutes les métadonnées de taille constante : les métadonnées d’épisode et les tables d’intervalles continuent de croître avec le jeu de données, et les shards de rang mélangés peuvent toucher des fichiers sources qui se chevauchent. Les pages mappées résidentes dans le système d’exploitation comptent également dans la RSS ; utilisez la taille d’ensemble proportionnelle (PSS) lors de la mesure de la mémoire partagée par les rangs. Les lectures Parquet élaguent les groupes de lignes à l’aide des statistiques d’épisode, puis filtrent pour ne conserver que l’épisode complet. Les groupes sans statistiques restent des candidats, de sorte que les fichiers sans bornes utiles peuvent encore nécessiter une lecture projetée de l’ensemble du fichier.

Les mainteneurs de jeux de données peuvent créer un sidecar à l'avance :

lerobot-build-mp4-sidecar \
  --repo-id=OWNER/DATASET \
  --revision=COMMIT_SHA \
  --data-root=hf://datasets/OWNER/DATASET@COMMIT_SHA \
  --output=/tmp/dataset-mp4-sidecar.npz

La publication est toujours explicite. Ajoutez --push uniquement après avoir validé le sidecar du jeu de données complet. Les sidecars de sous-ensembles ne peuvent pas être publiés.

Pour les dépôts de jeux de données, --push crée ou met à jour la branche dédiée lerobot-sidecars. La branche ou le tag du jeu de données source reste inchangé. L’entraînement y recherche un index indexé sur le commit de la source vidéo épinglée et le valide avant utilisation. Un tag et son commit résolu partagent le même index publié ; les caches locaux existants, indexés par révision, restent réutilisables. Les buckets publient toujours directement sous meta/mp4-sidecars/. La publication nécessite un accès en écriture au dépôt ou au bucket ; l’entraînement n’a besoin que d’un accès en lecture et ne crée jamais de branche ni ne téléverse de fichiers.

Le schéma de sidecar v3 conserve la chronologie de composition MP4 et les modifications liées au délai de l’encodeur, y compris pour les vidéos avec images B. Les sidecars plus anciens sont reconstruits automatiquement dans une entrée de cache distincte, indexée par révision ; ils doivent également être reconstruits avant toute publication explicite. Les vidéos réordonnées incluent la fin du dernier GOP dans chaque plage récupérée afin que les indices des images décodées restent contigus. Les chronologies de modification MP4 répétées ou à taux non unitaire sont rejetées plutôt que de renvoyer silencieusement des images mal alignées.

Configuration

Les valeurs par défaut de production sont :

OptionPar défautSignification
streaming_episode_pool_size32 / N chacunÉpisodes complets maximum mélangés par chaque travailleur
streaming_sampling_strategyremainingPondération des images restantes ou round_robin mélangé
streaming_prefetch_episodes8Épisodes récupérés à l'avance pour le pool de chaque travailleur
streaming_byte_budget_gb8Octets MP4 synthétisés maximaux par rang
streaming_decode_threads2Travailleurs d'assemblage d'échantillons parallèles et de décodage vidéo
streaming_decoded_queue_size8Échantillons décodés mis en mémoire tampon à l'avance, dans l'ordre du planificateur
video_decoder_cache_sizepool × camérasLimite LRU du décodeur vidéo ouvert par travailleur
streaming_native_http_connectionsunsetLimite native de connexions HTTP par rang
streaming_native_http_subranges1Sous-intervalles concurrents par lecture d'intervalle HTTP natif
streaming_sidecar_lock_timeout_s1800Secondes d'attente pour une autre construction de sidecar
max_num_shards (--num_workers)16Épisodes récupérés en parallèle par travailleur

Toutes les options, à l'exception de max_num_shards, sont des drapeaux --dataset.*. max_num_shards est un argument de StreamingLeRobotDataset ; lerobot-train le définit à partir de --num_workers (au moins 1).

Pour un mélange plus homogène des épisodes, optez pour --dataset.streaming_sampling_strategy=round_robin. Chaque tour mélange les épisodes actuellement résidents et échantillonne un cadre mélangé de chacun ; les épisodes admis pendant un tour rejoignent le suivant. Les deux stratégies conservent une couverture d'époque exactement une fois et les mêmes limites de ressources. Aucune n'est une permutation uniforme globale : le round-robin favorise les épisodes plus courts dans les préfixes finis et peut augmenter le renouvellement du décodeur et réduire le débit. L'ordre provient de --seed (valeur par défaut 1000), en conjonction avec l'initialisation de la politique et les autres générateurs aléatoires initialisés. Des graines différentes donnent des ordres d'épisodes et d'ancres différents, et la même graine répète l'ordre. --seed=null utilise la graine de streaming fixe 42. Conservez la même stratégie, --seed et les paramètres du pool lors de la reprise d'un point de contrôle. Le point de contrôle stocke la graine, de sorte qu'un simple --resume conserve l'ordre. Un point de contrôle issu d'une exécution démarrée avant que --seed n'atteigne le jeu de données de streaming utilisait la graine 42 pour son ordre : passez --seed=42 lors de sa reprise pour conserver cet ordre.

L'ensemble des épisodes actifs est limité à la fois par le nombre d'épisodes et par les tailles exactes des mini-MP4 synthétisés, calculées à partir du sidecar. Un épisode plus volumineux que le budget total du rang échoue avant que l'entraînement ne récupère sa charge utile. Les réservations couvrent les récupérations en attente, les préchargements terminés et les épisodes encore en cours de décodage. Lorsque le budget est saturé, le préchargement spéculatif est mis en pause et l'admission de remplacement attend que le dernier décodage libère ses octets. EpisodeByteCache.reserved_bytes signale ces réservations ; resident_bytes signale toutes les charges utiles terminées, même avant que leur premier échantillon ne soit demandé.

Il s’agit d’un budget de charge utile compressée, et non d’une limite de RSS du processus : les tampons de travail pour la récupération par plage/synthèse, l’état du décodeur, les tables Parquet et les tenseurs décodés nécessitent une mémoire supplémentaire. Le LRU du décodeur et la file d’échantillons décodés ont des limites distinctes. Commencez par un pool ou un budget plus petit sur les hôtes à mémoire limitée :

lerobot-train \
  --dataset.repo_id=OWNER/DATASET \
  --dataset.streaming=true \
  --dataset.streaming_episode_pool_size=16 \
  --dataset.streaming_prefetch_episodes=4 \
  --dataset.streaming_byte_budget_gb=4 \
  --dataset.streaming_decode_threads=2 \
  --dataset.streaming_decoded_queue_size=8 \
  --num_workers=4 \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

Pour un jeu de données complet stocké dans un bucket HF Storage, utilisez l’identifiant du bucket et le type de dépôt, ou passez l’URI du bucket sous la forme --dataset.root=hf://buckets/OWNER/BUCKET. Les métadonnées (meta/), les fichiers Parquet et vidéo doivent tous être présents à la racine de ce bucket :

lerobot-train \
  --dataset.repo_id=OWNER/BUCKET \
  --dataset.repo_type=bucket \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_bucket_streaming

Les jeux de données du Hub utilisent repo_type=dataset par défaut. Pour un jeu de données local complet, définissez --dataset.root sur son répertoire. L’entraînement résout les métadonnées et les charges utiles à partir de la même source ; aucun drapeau distinct pour l’emplacement des charges utiles en streaming n’est nécessaire.

Migration de reprise et mélange

Le lecteur de flux précédent utilisait un tampon de mélange de lignes borné. Le lecteur d’épisode, en revanche, possède un ordre de couverture exact et déterministe dérivé de la graine et de l’époque. La reprise depuis un point de contrôle restaure le décalage d’échantillon par rang en utilisant la taille de lot du point de contrôle. Avec plusieurs travailleurs, chaque travailleur dérive son propre décalage à partir de celui-ci, car le DataLoader prend les lots de ses travailleurs à tour de rôle. Modifier la taille du monde distribué, la taille de lot ou --num_workers modifie la propriété ou les limites de lot. Pour des comparaisons exactes à l’échantillon près, reprendre avec la même taille de monde, la même taille de lot et le même nombre de travailleurs. Conserver la même concurrence de récupération interne lors de la comparaison des performances.

Les métadonnées du point de contrôle n’identifient pas le sampler multi-travailleur antérieur. Ses points de contrôle ne peuvent pas être distingués de manière fiable par les vérifications de la taille du monde et de la taille du lot, et ne doivent pas être utilisés pour la reprise de streaming exacte à l’échantillon près. Lancez plutôt une nouvelle exécution à partir des poids de politique enregistrés. La garantie de reprise concerne l’ordre des ancrages, et non la rejouage bit à bit des transformations d’images stochastiques. Les transformations d’images aléatoires s’exécutent sur les threads de décodage, en parallèle avec le décodage, de sorte qu’une graine fixe ne reproduit pas les mêmes augmentations d’une exécution à l’autre.

Dépannage

SymptômeQue faire
Le premier lot prend beaucoup de tempsVérifiez les journaux pour une construction de sidecar. Réutilisez un cache partagé ou publiez explicitement un sidecar complet validé.
Un verrou de sidecar expirUn autre processus est peut-être encore en train d’indexer la même révision. Vérifiez qu’il est sain avant de supprimer un verrou obsolète.
Un rang ne possède aucune donnéeRéduisez le nombre de rangs afin que chaque rang possède au moins un épisode sélectionné.
Le budget d'octets est dépasséRéduisez la taille du pool d'épisodes, augmentez le budget d'octets par rang, ou utilisez une disposition de charge utile avec des plages d'épisodes plus petites.
Les lectures à distance ne peuvent pas s'authentifierRendre le jeton Hugging Face ou les identifiants fsspec disponibles pour chaque travailleur. Les sidecars n'intègrent jamais d'identifiants.
Les files d'attente de recharge sont élevéesComparez l’attente par lot p95/p99, réduisez la contention réseau, augmentez progressivement le préchargement et vérifiez que le sidecar correspond à la révision.
Update on GitHub