LeRobot documentation

Pipeline de Anotação

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

and get access to the augmented documentation experience

to get started

Pipeline de Anotação

lerobot-annotate monitora o vídeo de cada episódio com um modelo de visão-linguagem (VLM) e grava anotações em linguagem natural de volta no seu dataset. Ele preenche as duas colunas de linguagem da página Language Columns and Recipes — language_persistent e language_events — diretamente em data/chunk-*/file-*.parquet.

Em resumo: aponte-o para um dataset do LeRobot, e ele adiciona subtarefas, planos, memória, interjeições, fala e Q&A visual sobre os quais uma política pode ser treinada.

Como tudo se encaixa

  your dataset                  lerobot-annotate
  (LeRobotDataset v3.0)
        │
        ▼
  ┌─────────────────────────────────────────────────────┐
  │                    read episodes                     │
  └──────────────────────────┬──────────────────────────┘
                             │
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                     ▼
  ┌──────────┐      ┌───────────────┐        ┌──────────┐       one shared Qwen-VL
  │   plan   │      │ interjections │        │   vqa    │  ◀──   server (vLLM, OpenAI
  └────┬─────┘      └───────┬───────┘        └────┬─────┘        API) drives all three
       └────────────────────┼─────────────────────┘
                            │   each module stages raw JSONL
                            ▼   into .annotate_staging/
                  ┌─────────────────┐
                  │    validator    │  ◀──  checks everything
                  └────────┬────────┘
                           ▼
                  ┌─────────────────┐
                  │     writer      │
                  └────────┬────────┘
                           ▼
              data/chunk-*/file-*.parquet
              (+ meta/info.json tools)

Três módulos (plan, interjections, vqa) se comunicam com um único VLM compartilhado. Cada módulo grava sua saída em disco, um validador a verifica e um único escritor reescreve os shards do dataset no local.

O que o pipeline produz

Cada módulo emite alguns tipos de anotação (“estilos”), roteados para uma das duas colunas de linguagem:

Estilo / átomoColunaMódulo
subtask (estilo Pi0.7, “como, não o quê”)language_persistentplan
plan (inicial + atualização em interjeição)language_persistentplan
memory (compressão no estilo MEM)language_persistentplan
task_aug (reformulações da tarefa)language_persistentplan
interjectionlanguage_eventsinterjections
áudio de chamada de ferramenta (style=null, say)language_eventsinterjections
vqa (par de usuário / assistente)language_eventsvqa

Como as subtarefas são geradas

O módulo plan não solicita ao VLM os subtarefas de uma só vez. Em vez disso, ele usa um fluxo de duas etapas descrever → segmentar:

  1. Descrever — o VLM narra apenas o que ele realmente vê na câmera escolhida (sem suposições sobre a tarefa).
  2. Segment — essa descrição é reenviada e o VLM divide o episódio em subtarefas atômicas consecutivas.

Ambas as passadas enxergam o episódio como folhas de contato com carimbo de tempo — quadros amostrados a frames_per_second (0,5s por padrão) e empacotados em grades JPEG com o tempo de cada quadro gravado em seu canto, para que o VLM cite os tempos exatos das fronteiras diretamente. Isso é muito mais barato em tokens de visão do que uma imagem por quadro, então a amostragem pode permanecer densa; episódios mais longos que max_frames_per_prompt são divididos em janelas na mesma densidade e mesclados. Ambos os prompts também carregam uma definição causal de fronteira de evento (um novo evento começa quando um objeto é segurado / é solto / atinge um novo local / uma tampa muda de estado / o conteúdo se move) para afiar onde os cortes são feitos.

Opcionalmente, uma terceira passagem de seeded-relabel (--plan.subtask_seeded_relabel) revisa cada trecho com as folhas de contato dos segmentos anterior/atual/próximo e corrige o rótulo de forma mínima, usando o primeiro rótulo como prior — ela mantém as fronteiras fixas e apenas afina a redação, ao custo de uma chamada extra por subtarefa.

Os spans resultantes são então unidos em uma cobertura completa do episódio, sem lacunas, de modo que cada quadro tenha exatamente um subtarefa ativa. Consulte Running on Hugging Face Jobs para as configurações de produção (câmera única, contatos com carimbo de data e hora, geração de subtarefas com janelas automáticas).

Ferramentas

The writer does not add a tools column to the parquet. The tool catalog lives in meta/info.json["tools"] instead (see Tools). After every run, the pipeline makes sure the canonical say schema is in that list, keeping any tools you declared beforehand.

Quer adicionar sua própria ferramenta? Edite meta/info.json["tools"] diretamente — o pipeline preserva o que já estiver lá. Isso torna a ferramenta visível para o chat template, permitindo que o modelo aprenda a gerar a chamada. A camada de runtime que realmente executa uma chamada gerada (o protocolo Tool / TOOL_REGISTRY em src/lerobot/tools/) ainda não está implementada — o documento Tools marca essas partes como não implementadas.

Executando em Hugging Face Jobs

Anotar um dataset real exige uma GPU grande o suficiente para servir o VLM, então o lerobot-annotate pode se despachar para o Hugging Face Jobs — da mesma forma que o lerobot-train. Adicione --job.target=<flavor> ao comando exato que você executaria localmente e ele será executado naquele hardware:

hf auth login   # once

uv run lerobot-annotate \
    --repo_id=user/my_dataset \
    --new_repo_id=user/my_dataset_annotated \
    --push_to_hub=true \
    --vlm.model_id=Qwen/Qwen3.6-27B \
    --vlm.num_gpus=1 \
    --vlm.serve_command="vllm serve Qwen/Qwen3.6-27B --tensor-parallel-size 1 \
        --max-model-len 32768 --gpu-memory-utilization 0.8 \
        --uvicorn-log-level warning --port {port}" \
    --vlm.serve_ready_timeout_s=1800 \
    --vlm.chat_template_kwargs='{"enable_thinking": false}' \
    --job.target=h200

Isso envia um trabalho de GPU única h200 que:

  1. começa a partir da imagem vllm/vllm-openai e instala o lerobot por cima,
  2. inicia um servidor vLLM por GPU e o controla por meio da API compatível com a OpenAI,
  3. executa os módulos plan / interjections / vqa em todo o conjunto de dados,
  4. com --push_to_hub=true, faz o upload do resultado para --new_repo_id (ou de volta para --repo_id no local, se você deixar isso não definido).

O comando transmite os logs do job; Ctrl-C desconecta sem cancelá-lo. Liste os sabores disponíveis e seus preços com hf jobs hardware.

O Qwen3.6 é distribuído com o modo de raciocínio ativado, o que consome o orçamento de tokens de que o anotador precisa para sua resposta em JSON — --vlm.chat_template_kwargs='{"enable_thinking": false}' desativa esse modo. Sem --push_to_hub=true, o conjunto de dados anotado é descartado quando o pod é encerrado.

Opções de trabalho

FlagPadrãoO que ele faz
--job.targetlocalFlavor de HF Jobs para executar (por exemplo, h200, h200x4). Omitido/local executa aqui.
--job.imagevllm/vllm-openai:latestImagem de runtime para o pod.
--job.timeout2hLimite de tempo real. Aumente-o para conjuntos de dados grandes.
--job.detachfalseEnvie e saia em vez de transmitir logs.
--job.lerobot_refmainRef do Git do lerobot instalado no pod — aponte para um branch para testar alterações.
--job.tags[]Tags extras no job e em qualquer dataset que ele envia (lerobot é sempre adicionado).

Para um conjunto de dados maior, escale para h200x4 e aumente --vlm.parallel_servers / --vlm.num_gpus de forma correspondente, e dê ao trabalho mais folga com, por exemplo, --job.timeout=8h.

Execuções remotas exigem --repo_id (o pod busca o dataset do Hub; --root nomeia um diretório que existe apenas na sua máquina). Um dataset que existe apenas no seu cache local é enviado primeiro para um repositório privado.

Opções principais

Estas são as flags que você usará com mais frequência. Execute lerobot-annotate --help para ver o restante; os valores padrão são ajustados para episódios curtos de manipulação.

Dataset de entrada/saída

FlagPadrãoO que ele faz
--repo_id—Conjunto de dados do Hub para anotar (baixado se --root não estiver definido).
--root—Anote um diretório de dataset local em vez disso.
--new_repo_id—Envie o resultado para um novo repositório (mantém o repositório de origem intacto).
--push_to_hubfalseEnvie após a anotação (para --new_repo_id, caso contrário, de volta para --repo_id).
--only_episodestodosAnote apenas esses índices de episódios (útil para uma execução de teste).
--seed1729Inicializa os RNGs que selecionam os timestamps das interjeições + os tipos de perguntas de VQA.

Quais módulos são executados

Cada módulo está ativado por padrão e pode ser alternado independentemente (defina como false para ignorá-lo, por exemplo, para iterar em um módulo de cada vez):

FlagPadrãoDesativa
--plan.enabledtruesubtasks + plan + memory + task_aug
--interjections.enabledtrueinterjeições + átomos de fala
--vqa.enabledtrueos pares de VQA

O VLM ( --vlm.* )

FlagPadrãoO que ele faz
--vlm.model_idQwen/Qwen3.6-27BO modelo a ser servido e o prompt.
--vlm.camera_keyprimeiro images.*Em qual câmera cada prompt é baseado.
--vlm.serve_commandautoO comando exato vllm serve … (defina o tamanho do TP, a memória da GPU e o --max-model-len aqui).
--vlm.parallel_servers1Servidores independentes para roteamento round-robin (um por GPU).
--vlm.num_gpus0GPUs por servidor (0 = uma em cada).
--vlm.client_concurrency16Solicitações em andamento em todos os servidores.
--vlm.max_new_tokens512Limite de geração por chamada.
--vlm.temperature0.2Temperatura de amostragem.
--vlm.reasoning_effortnullDica de orçamento de raciocínio (low/medium/high) encaminhada para servidores compatíveis com a OpenAI.

Subtarefas / plano / memória ( --plan.* )

FlagPadrãoO que ele faz
--plan.frames_per_second2.0Taxa de amostragem de quadros para as contact sheets (2.0 = um quadro a cada 0,5s).
--plan.max_frames_per_prompt60Orçamento de quadros por chamada de VLM. Episódios cuja amostragem excede esse valor são automaticamente enjanelados na mesma densidade e, em seguida, costurados.
--plan.contact_sheet_columns5Colunas por grade de contact sheet (contact_sheet_frames_per_sheet tiles, linha do tempo em ordem majoritária).
--plan.plan_max_steps8Limite superior de subtarefas por episódio.
--plan.subtask_describe_firsttrueExecute a passagem de grounding describe→segment (melhor qualidade de subtarefa; +1 chamada/episódio).
--plan.subtask_seeded_relabelfalseSegunda passada: reetiquete cada subtarefa a partir de suas folhas de contato anterior/atual/próxima, iniciando com o primeiro rótulo (+1 chamada/subtarefa).
--plan.subtask_relabel_frames5Frames amostrados uniformemente por folha de segmento na passagem de reetiquetagem (usado apenas quando subtask_seeded_relabel=true).
--plan.emit_plantrueEmita as linhas numeradas de plan (false = apenas subtarefas + memória).
--plan.emit_memorytrueEmita as linhas de memory (false = apenas subtarefas + plano); simétrico a emit_plan.
--plan.n_task_rephrasings10Quantas reformulações de task_aug emitir (0 desativa).
--plan.derive_task_from_videoif_shortUse a tarefa do dataset como está (off), apenas quando estiver ausente/curta (if_short) ou sempre rederive do vídeo (always).

Interjeições + VQA

FlagPadrãoO que ele faz
--interjections.max_interjections_per_episode3Limite de pares de interjeição/fala por episódio.
--vqa.vqa_emission_hz1.0Com que frequência os pares VQA são emitidos.
--vqa.restrict_to_default_camerafalseBaseie o VQA apenas em --vlm.camera_key (caso contrário, todas as câmeras).
--vqa.coordinate_scale1000Grade de bbox/keypoint do VLM, armazenada como [0, 1] (none = px).
--executor.episode_parallelism16Episódios processados simultaneamente dentro de cada fase.

Contribuindo com novos módulos

O pipeline foi projetado para crescer, e contribuições são muito bem-vindas — um módulo totalmente novo (por exemplo, rastreamentos de trajetória ou affordances), um novo template de prompt, um fluxo de grounding mais inteligente ou correções de qualidade nos módulos existentes plan / interjections / vqa.

Cada módulo fica em src/lerobot/annotations/steerable_pipeline/modules/, compartilha o cliente VLM e o cache de keyframes, grava sua saída bruta na árvore de staging e se conecta ao executor como sua própria fase. Tem uma ideia? Abra uma issue ou PR em o repositório.

Como as receitas consomem a saída

As anotações destinam-se a serem lidas por receitas (veja Colunas de idioma e receitas). Tipicamente:

  • Os ramos de baixo nível / alto nível / atualização de memória leem subtask / plan / memory de language_persistent.
  • um ramo de interjeição-resposta lê eventos de interjection e o átomo de fala correspondente (mesclados em um único turno do assistente via tool_calls_from) e a atualização correspondente de plan no mesmo timestamp.
  • Um ramo de VQA lê os pares (vqa, user) e (vqa, assistant) de language_events.

Por que estado e eventos são separados

Duas ideias moldam o design:

  1. Estado persistente vs. eventos exatos. Linhas persistentes (subtask, plan, memory) se aplicam a todo o episódio e respondem à pergunta “o que é verdade agora?”. Linhas de eventos (interjection, vqa, fala) aparecem apenas no frame cujo timestamp corresponde. Os timestamps são copiados diretamente do parquet de origem — nunca recalculados em ponto flutuante.
  2. Uma única passagem do VLM. Os três módulos compartilham um único cliente de VLM (o cliente compatível com a OpenAI que se comunica com o servidor vLLM do job), de modo que você paga por um único carregamento de modelo por dataset, e não por três.

Executando novamente um único módulo

Cada módulo grava sua saída bruta em <root>/.annotate_staging/episode_{N:06d}/<module>.jsonl. Isso torna a iteração de prompts barata: reexecutar um módulo sobrescreve apenas o JSONL dele, e em seguida o writer recompor o parquet final. Desative os módulos que você não deseja com --plan.enabled=false (e da mesma forma --interjections.enabled / --vqa.enabled) para testá-los um por um.

O que o validador verifica

Antes de o writer ser executado, StagingValidator confirma:

  • cada linha de evento corresponde exatamente a um timestamp real de quadro;
  • não há pares de fala / interjeição que fiquem órfãos;
  • plan é atualizado a cada timestamp de interjeição;
  • linhas de memory caem nas fronteiras de subtarefas (um aviso, não um erro);
  • cada conteúdo content do assistente VQA é um JSON válido em um dos formatos bbox / keypoint / count / attribute / spatial;
  • cada linha vai para a coluna escolhida por column_for_style(style).

Qualquer erro interrompe o escritor. Passe --skip_validation=true para ignorar durante a depuração.

De onde vêm as ideias de cada módulo

  • plan — subtarefas. Hi Robot (Shi 2025) para granularidade atômica (“pegar uma folha de alface”, “colocar a tigela na caixa”); Pi0.7 (Physical Intelligence 2025) para detalhes de “como, não o quê”.
  • plan — memória. MEM (Torne 2026): mantenha apenas as informações mínimas relevantes — preserve os resultados, descarte atributos específicos.
  • interjections. Taxonomia de cenários do Hi Robot: tarefa negativa, correção situada, restrição específica, preferência. A fala é um átomo exclusivo de chamada de ferramenta (tool_calls=[{type:function, function:{name:"say", arguments:{text:...}}}]).
  • vqa. ECoT (Zawalski 2024) para recursos fundamentados (bounding boxes [x_min, y_min, x_max, y_max] e keypoints, armazenados como frações de imagem em [0, 1]) e Steerable VLA Policies (Zhao 2025) para fundamentação em múltiplos níveis de abstração. O Pi0.7 também fundamenta respostas em diferentes níveis de abstração.

Ao melhorar um módulo, ajuste seu modelo de prompt em src/lerobot/annotations/steerable_pipeline/prompts/ em vez de reescrevê-lo do zero.

Aproximadamente quanto custa

Por episódio, o pipeline faz cerca de plan_max_steps chamadas de plano, max_interjections_per_episode chamadas de interjeição e vqa_emission_hz × episode_seconds chamadas de VQA. Com os padrões (8 subtarefas, 3 interjeições, 1 Hz × 3 pares) em um episódio de 30 segundos, isso resulta em ~50 chamadas de VLM.

O armazenamento permanece pequeno: language_persistent tem no máximo algumas dezenas de KB por episódio (o parquet codifica por dicionário a única entrada que se repete entre os frames) e language_events está vazio na maioria dos frames — seu tamanho escala com o número de emissões, não com num_frames × num_emissions.

Update on GitHub