LeRobot documentation

LeRobotDataset v3.0

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

LeRobotDataset v3.0

LeRobotDataset v3.0 é um formato padronizado para dados de aprendizado de robôs. Ele fornece acesso unificado a dados de séries temporais multimodais, sinais sensório-motores e vídeo de múltiplas câmeras, além de metadados ricos para indexação, busca e visualização no Hugging Face Hub.

Para ver o que um dataset nesse formato contém antes de ler sobre seu layout, abra um no LeRobot Dataset Visualizer: gravações de câmera, gráficos de estado e ação sincronizados e todos os episódios, no navegador. Veja Explore a Dataset in the Browser.

Este documento irá guiá-lo a:

  • Entender o design e o layout de diretórios da v3.0
  • Registre um conjunto de dados e envie-o para o Hub
  • Carregue conjuntos de dados para treinamento com LeRobotDataset
  • Transmita conjuntos de dados sem baixar usando StreamingLeRobotDataset
  • Aplicar transformações de imagem para aumento de dados durante o treinamento
  • Migrar conjuntos de dados existentes do v2.1 para o v3.0
  • Experimente com outros formatos e implementações de LeRobotDataset, como Lance

Novidades na v3

  • Armazenamento baseado em arquivos: muitos episódios por arquivo Parquet/MP4 (a v2 usava um arquivo por episódio).
  • Metadados relacionais: As fronteiras dos episódios e as consultas são resolvidas por meio de metadados, não por nomes de arquivos.
  • Streaming nativo do Hub: Consuma datasets diretamente do Hub com StreamingLeRobotDataset.
  • Menor pressão no sistema de arquivos: Menos arquivos, maiores ⇒ inicialização mais rápida e menos problemas em escala.
  • Organização unificada: Estrutura de diretórios limpa com modelos de caminho consistentes entre dados e vídeos.
  • Backends de armazenamento de datasets: Datasets também podem ser armazenados como tabelas LanceDB para acesso aleatório e indexação diretamente do armazenamento de objetos (veja backend de dataset LanceDB).

Instalação

LeRobotDataset v3.0 está incluído em lerobot >= 0.4.0. Siga o Guia de Instalação para instalá-lo.

Registrar um conjunto de dados

Execute o comando abaixo para gravar um conjunto de dados com o SO-101 e enviá-lo para o Hub:

lerobot-record \
  --robot.type=so101_follower \
  --robot.port=/dev/tty.usbmodem585A0076841 \
  --robot.id=my_awesome_follower_arm \
  --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 1920, height: 1080, fps: 30}}" \
  --teleop.type=so101_leader \
  --teleop.port=/dev/tty.usbmodem58760431551 \
  --teleop.id=my_awesome_leader_arm \
  --display_data=true \
  --dataset.repo_id=${HF_USER}/record-test \
  --dataset.num_episodes=5 \
  --dataset.single_task="Grab the black cube" \
  --dataset.streaming_encoding=true \
  --dataset.encoder_threads=2
# Optional: --dataset.rgb_encoder.vcodec=auto

Veja o guia de gravação para mais detalhes.

Design do formato

Um princípio central da v3 é desacoplar o armazenamento da API do usuário: os dados são armazenados de forma eficiente (poucos arquivos grandes), enquanto a API pública expõe um acesso intuitivo no nível de episódio.

v3 tem três pilares:

  1. Dados tabulares: Sinais de baixa dimensão e alta frequência (estados, ações, carimbos de data/hora) armazenados em Apache Parquet. O acesso é feito por mapeamento de memória ou streaming por meio da pilha datasets.
  2. Dados visuais: Quadros de câmera concatenados e codificados em MP4. Os quadros do mesmo episódio são agrupados; os vídeos são divididos por câmera para tamanhos práticos.
  3. Metadados: registros JSON/Parquet que descrevem o esquema (nomes dos recursos, dtypes, formas), taxas de quadros, estatísticas de normalização e segmentação de episódios (deslocamentos de início/fim para os arquivos Parquet/MP4 compartilhados).

Para escalar para milhões de episódios, linhas tabulares e quadros de vídeo de múltiplos episódios são concatenados em arquivos maiores. As visões específicas de cada episódio são reconstruídas por meio de metadados, e não por fronteiras de arquivo.

LeRobotDataset v3 diagram
De datasets baseados em episódios para datasets baseados em arquivos

Estrutura do diretório (simplificada)

  • meta/info.json: esquema canônico (recursos, formas/tipos de dados), FPS, versão da base de código e modelos de caminho para localizar os shards de dados/vídeos.
  • meta/stats.json: estatísticas globais dos recursos (média/desvio padrão/mínimo/máximo) usadas para normalização; expostas como dataset.meta.stats.
  • meta/tasks.parquet: descrições de tarefas em linguagem natural mapeadas para IDs inteiros para políticas condicionadas por tarefa.
  • meta/episodes/: registros por episódio (comprimentos, tarefas, deslocamentos) armazenados como Parquet em blocos para escalabilidade.
  • data/: shards Parquet quadro a quadro; cada arquivo geralmente contém muitos episódios.
  • videos/: shards MP4 por câmera; cada arquivo geralmente contém muitos episódios.

Carregue um conjunto de dados para treinamento

LeRobotDataset retorna dicionários Python de tensores do PyTorch e se integra ao torch.utils.data.DataLoader. Aqui está um exemplo de código mostrando seu uso:

import torch
from lerobot.datasets import LeRobotDataset

repo_id = "yaak-ai/L2D-v3"

# 1) Load from the Hub (cached locally)
dataset = LeRobotDataset(repo_id)

# 2) Random access by index
sample = dataset[100]
print(sample)
# {
#   'observation.state': tensor([...]),
#   'action': tensor([...]),
#   'observation.images.front_left': tensor([C, H, W]),
#   'timestamp': tensor(1.234),
#   ...
# }

# 3) Temporal windows via delta_timestamps (seconds relative to t)
delta_timestamps = {
    "observation.images.front_left": [-0.2, -0.1, 0.0]  # 0.2s and 0.1s before current frame
}

dataset = LeRobotDataset(repo_id, delta_timestamps=delta_timestamps)

# Accessing an index now returns a stack for the specified key(s)
sample = dataset[100]
print(sample["observation.images.front_left"].shape)  # [T, C, H, W], where T=3

# 4) Wrap with a DataLoader for training
batch_size = 16
data_loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size)

device = "cuda" if torch.cuda.is_available() else "cpu"
for batch in data_loader:
    observations = batch["observation.state"].to(device)
    actions = batch["action"].to(device)
    images = batch["observation.images.front_left"].to(device)
    # model.forward(batch)

Transmitir um conjunto de dados durante o treinamento

Defina --dataset.streaming=true para treinar sem primeiro baixar os arquivos Parquet e de vídeo do conjunto de dados. Os episódios são obtidos sob demanda para um pool em memória limitado, e cada quadro selecionado ainda é visitado exatamente uma vez por época:

lerobot-train \
  --dataset.repo_id=yaak-ai/L2D-v3 \
  --dataset.streaming=true \
  --policy.type=act \
  --output_dir=outputs/train/act_streaming

O mesmo leitor está disponível em Python, para conjuntos de dados do Hub e para conjuntos de dados armazenados em um HF Storage Bucket:

from lerobot.datasets import StreamingLeRobotDataset

dataset = StreamingLeRobotDataset("yaak-ai/L2D-v3")
bucket_dataset = StreamingLeRobotDataset("my-org/my-bucket", repo_type="bucket")

A partir da CLI, selecione um bucket com --dataset.repo_type=bucket ou --dataset.root=hf://buckets/my-org/my-bucket.

Veja Streaming do Conjunto de Dados de Treinamento para saber como os workers e shards se mapeiam para a concorrência de streaming, o índice sidecar do MP4, as configurações de memória, o retomar e a solução de problemas.

StreamingLeRobotDataset
Transmita diretamente do Hub para treinamento em tempo real.

Transformações de imagem

As transformações de imagem são aumentações de dados aplicadas aos quadros da câmera durante o treinamento para melhorar a robustez e a generalização do modelo. O LeRobot suporta diversas transformações, incluindo ajustes de brilho, contraste, saturação, matiz e nitidez.

Usando transforms durante a criação/gravação do dataset

Atualmente, as transformações são aplicadas apenas durante o treinamento, e não durante a gravação. Ao criar ou gravar um conjunto de dados, as imagens brutas são armazenadas sem transformações. Isso permite que você experimente diferentes aumentações posteriormente, sem precisar regravar os dados.

Adicionando transforms a conjuntos de dados existentes (API)

Use o parâmetro image_transforms ao carregar um conjunto de dados para treinamento:

from lerobot.datasets import LeRobotDataset
from lerobot.transforms import ImageTransforms, ImageTransformsConfig, ImageTransformConfig

# Option 1: Use default transform configuration (disabled by default)
transforms_config = ImageTransformsConfig(
    enable=True,  # Enable transforms
    max_num_transforms=3,  # Apply up to 3 transforms per frame
    random_order=False,  # Apply in standard order
)
transforms = ImageTransforms(transforms_config)

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=transforms
)

# Option 2: Create custom transform configuration
custom_transforms_config = ImageTransformsConfig(
    enable=True,
    max_num_transforms=2,
    random_order=True,
    tfs={
        "brightness": ImageTransformConfig(
            weight=1.0,
            type="ColorJitter",
            kwargs={"brightness": (0.7, 1.3)}  # Adjust brightness range
        ),
        "contrast": ImageTransformConfig(
            weight=2.0,  # Higher weight = more likely to be selected
            type="ColorJitter",
            kwargs={"contrast": (0.8, 1.2)}
        ),
        "sharpness": ImageTransformConfig(
            weight=0.5,  # Lower weight = less likely to be selected
            type="SharpnessJitter",
            kwargs={"sharpness": (0.3, 2.0)}
        ),
    }
)

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=ImageTransforms(custom_transforms_config)
)

# Option 3: Use pure torchvision transforms
from torchvision.transforms import v2

torchvision_transforms = v2.Compose([
    v2.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1),
    v2.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),
])

dataset = LeRobotDataset(
    repo_id="your-username/your-dataset",
    image_transforms=torchvision_transforms
)

Tipos de transformação disponíveis

O type de um transform pode ser qualquer classe de torchvision.transforms.v2, por exemplo:

  • ColorJitter: Ajusta brilho, contraste, saturação e matiz
  • SharpnessJitter: Ajusta aleatoriamente a nitidez da imagem
  • RandomAffine, RandomRotation: jitter geométrico
  • GaussianNoise, MotionBlur, JPEGCompression, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection, PlanckianJitter: efeitos de câmera e iluminação vistos em robôs reais
  • Identity: Sem transformação (útil para testes)

Qualquer outra classe do torchvision.transforms.v2 também pode ser nomeada como um type (por exemplo, RandomResizedCrop ou GaussianBlur); ela é aplicada um exemplo por vez. Você também pode passar um transform do torchvision diretamente para o parâmetro image_transforms.

Agora, todas as transformações são processadas em lote, e as mesmas transformações em lote são executadas em ambos os backends. As classes por amostra (SharpnessJitter, GaussianNoise, MotionBlur, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection, PlanckianJitter, RandomSubsetApply) e make_transform_from_config permanecem importáveis como interfaces obsoletas sobre seus substitutos Batched* e emitem um aviso ao serem usadas; elas serão removidas em uma versão futura. ImageTransforms é um torch.nn.Module simples que recebe um tensor (C, H, W) ou (T, C, H, W). As configurações que nomeiam uma transformação por type não são afetadas.

Mudança de comportamento para receitas existentes. O treinamento lê os quadros como uint8, e as transformações do LeRobot, exceto ColorJitter, RandomAffine e RandomRotation, costumavam retornar a entrada não-float sem alterações. Portanto, as receitas que ativavam GaussianNoise, MotionBlur, GaussianPatchBrightness, RandomShadow, CoarseDropout, GammaCorrection ou PlanckianJitter estavam sendo treinadas sem elas. Agora, elas são aplicadas em ambos os backends, de modo que uma receita assim treina de forma diferente do que antes: ela recebe a aumentação que solicitou. As transformações que já estavam sendo aplicadas permanecem inalteradas, exceto pelo arredondamento, pois o caminho em lote calcula em float e arredonda uma única vez no final, em vez de após cada transformação.

Opções de configuração

  • enable: Ativa/desativa as transformações (padrão: False)
  • max_num_transforms: Número máximo de transforms aplicados por frame (padrão: 3)
  • random_order: Aplique as transformações em ordem aleatória em vez da ordem padrão (padrão: False)
  • weight: Probabilidade de amostragem para cada transformação (maior = mais provável; se a soma dos pesos não for 1, eles serão normalizados)
  • kwargs: Parâmetros específicos do Transformers (por exemplo, intervalo de brilho)
  • backend: Onde as transformações são executadas durante o treinamento, "dataloader" (padrão) ou "gpu"; veja abaixo
  • gpu_compile: No backend "gpu", aplica torch.compile às transformações em lote (padrão: True)
  • gpu_chunk_size: No backend "gpu", aumenta esse número de amostras por vez para limitar a memória transitória (padrão: 32)

Executando transforms na GPU

Por padrão, as transformações são executadas por amostra nos workers do DataLoader. Com várias câmeras, isso costuma ser a tarefa mais cara que os workers executam, e a GPU acaba esperando por dados. Defina backend=gpu para que os workers façam apenas a decodificação e apliquem as mesmas transformações a cada lote no dispositivo da política:

lerobot-train \
  --dataset.repo_id=your-username/your-dataset \
  --dataset.image_transforms.enable=true \
  --dataset.image_transforms.backend=gpu \
  --policy.type=act \
  --policy.device=cuda

A randomização permanece inalterada: cada amostra sorteia suas próprias transformações e parâmetros, e os quadros do histórico de uma câmera os compartilham. O aumento de dados é aplicado apenas aos lotes de treinamento, antes do pré-processador da política; os lotes de avaliação, os rollouts e os checkpoints salvos permanecem intactos.

Coisas a saber:

  • Exige um acelerador; --policy.device=cpu é rejeitado. Um dataset construído manualmente com seu próprio image_transforms chamável também é rejeitado, pois esses são executados nos workers.
  • Suporta todos os tipos de transform que o backend do dataloader suporta. Tipos sem implementação em lote (JPEGCompression, e classes do torchvision diferentes de ColorJitter, RandomAffine, RandomRotation e Identity) são aplicados um por vez dentro do lote, portanto não recebem a aceleração; JPEGCompression também faz ida e volta pela CPU. Um transform que altera o tamanho do quadro é rejeitado em um lote com mais de uma amostra.
  • Reproduzível a partir de --seed, com cada processo semeado de forma diferente. O aumento não consome o fluxo aleatório da política, e gpu_compile e gpu_chunk_size não alteram o que é aplicado.
  • Ambos os backends executam as mesmas transformações em lote em float e arredondam apenas uma vez no final, de modo que uma amostra aumentada em um é igual à mesma amostra aumentada no outro, desde que os parâmetros sejam os mesmos.
  • gpu_compile custa de 15 a 30 s uma vez por forma de imagem. A memória de pico é de aproximadamente duas cópias em ponto flutuante das imagens do lote, além de algumas cópias de um único bloco; reduza gpu_chunk_size se a memória estiver limitada. random_order=true multiplica o tempo de transformação por max_num_transforms.

Migrar da v2.1 para a v3.0

Um conversor agrega arquivos por episódio em shards maiores e grava offsets/metadata dos episódios. Converta seu conjunto de dados usando as instruções abaixo.

# Convert an existing v2.1 dataset hosted on the Hub:
python -m lerobot.scripts.convert_dataset_v21_to_v30 --repo-id=<HF_USER/DATASET_ID>

O que ele faz

  • Agrega arquivos parquet: episode_000000.parquet, episode_000001.parquet, … → file-000.parquet, …
  • Agrega arquivos mp4: episode_000000.mp4, episode_000001.mp4, … → file-000.mp4, …
  • Atualiza meta/episodes/* (Parquet em blocos) com comprimentos por episódio, tarefas e deslocamentos de bytes/quadros.

Problemas comuns

Sempre chame finalize() antes de fazer push

Ao criar ou gravar conjuntos de dados, você deve chamar dataset.finalize() para fechar corretamente os escritores parquet. Consulte o PR #1903 para mais detalhes.

from lerobot.datasets import LeRobotDataset

# Create dataset and record episodes
dataset = LeRobotDataset.create(...)

for episode in range(num_episodes):
    # Record frames
    for frame in episode_data:
        dataset.add_frame(frame)
    dataset.save_episode()

# Call finalize() when done recording and before push_to_hub()
dataset.finalize()  # Closes parquet writers, writes metadata footers
dataset.push_to_hub()

Por que isso é necessário?

O Dataset v3.0 usa gravação incremental em parquet com metadados em buffer para eficiência. O método finalize():

  • Limpa os metadados de episódio em buffer para o disco
  • Fecha os escritores parquet para gravar os metadados do rodapé; caso contrário, os arquivos parquet ficarão corrompidos
  • Garante que o conjunto de dados seja válido para carregamento

Sem chamar finalize(), seus arquivos parquet ficarão incompletos e o dataset não será carregado corretamente.

Outros formatos e implementações

Backend de dataset do LanceDB

Um dataset v3 também pode ser armazenado como tabelas LanceDB. O LanceDB é um data lakehouse multimodal construído sobre o formato Lance para padrões de acesso de IA: acesso aleatório rápido a qualquer linha e seus bytes de vídeo, além de índices escalares, vetoriais e de texto completo nas mesmas tabelas, permitindo que EDA, mineração, curadoria e treinamento sejam executados diretamente do armazenamento de objetos. O layout mantém meta/ inalterado e adiciona uma tabela frames para os dados tabulares e uma tabela videos que armazena os MP4s como blobs. "storage_format": "lance" em meta/info.json seleciona o leitor; a API LeRobotDataset, os itens e o pipeline de treinamento permanecem inalterados.

  • Treine diretamente do Hub ou dos Buckets de armazenamento do HF com os GPUs alimentados. Os quadros aleatórios são lidos no local, apenas os bytes necessários para cada lote, de modo que cada lote é uma embaralhamento global e nada é baixado antes.
  • Converte com um custo aproximado de uma cópia de arquivo. Os vídeos não são re-codificados, portanto os itens são idênticos ao nível de bits em relação à fonte. O DROID converte em 34 minutos.
  • Engenharia de recursos sem cópia. Adicione colunas, como pontuações, incorporações ou rótulos corrigidos, às tabelas que o treinador lê, sem reescrever os vídeos ou copiar o conjunto de dados, e depois filtre e pesquise neles usando os índices. O leitor ignora colunas que não conhece.

Para saber como isso funciona, benchmarks e exemplos, consulte o guia LanceDB LeRobot.

Os recursos de câmera devem ser baseados em vídeo. Conjuntos de dados que armazenam quadros de câmera como imagens não são suportados pelo leitor e precisam ser re-codificados como vídeo antes da conversão.

A leitura de datasets Lance requer o extra lancedb (pip install "lerobot[lancedb]"). O formato é somente leitura: registre e edite datasets no formato padrão e, em seguida, converta-os com o pacote lerobot-lancedb:

pip install "lerobot-lancedb"
lerobot-lance-convert --repo-id lerobot/pusht --out ./pusht-lance  # or --root <local dataset dir>

O carregamento usa a mesma classe:

from lerobot.datasets import LeRobotDataset

# Hub dataset repo
dataset = LeRobotDataset("lance-format/pusht-lance")

# HF Storage Bucket, read in place
dataset = LeRobotDataset("my-org/pusht-lance", repo_type="bucket")

# the same bucket as a root URI (other object store URIs work the same way)
dataset = LeRobotDataset("lerobot/pusht", root="hf://buckets/my-org/pusht-lance")

As opções de Bucket do HF Storage funcionam no lerobot-train:

lerobot-train \
  --dataset.repo_id=my-org/pusht-lance \
  --dataset.repo_type=bucket \
  --policy.type=act

A visualização funciona com os dois visualizadores. lerobot-dataset-viz --repo-id lance-format/pusht-lance --episode-index 0 abre um episódio no Rerun como de costume, e --display-mode foxglove o envia para o Foxglove via WebSocket. De qualquer forma, apenas os quadros na tela são obtidos, sem exportação ou download de MCAP.

Update on GitHub