LeRobot documentation

Pipeline Anotasi

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 Anotasi

lerobot-annotate memantau video setiap episode dengan model bahasa-visi (VLM) dan menulis anotasi bahasa alami kembali ke dataset Anda. Alat ini mengisi dua kolom bahasa dari halaman Language Columns and Recipes — language_persistent dan language_events — langsung ke data/chunk-*/file-*.parquet.

Singkatnya: arahkan ke dataset LeRobot, dan ia menambahkan subtask, rencana, memori, interupsi, ucapan, serta visual Q&A yang dapat digunakan untuk melatih kebijakan.

Bagaimana semuanya saling terhubung

  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)

Tiga modul (plan, interjections, vqa) semuanya berkomunikasi dengan satu VLM yang berbagi. Setiap modul menyimpan outputnya ke disk, validator memeriksanya, dan satu writer tunggal menulis ulang shard dataset secara in-place.

Apa yang dihasilkan oleh pipeline

Setiap modul mengeluarkan beberapa jenis anotasi (“gaya”), yang diarahkan ke salah satu dari dua kolom bahasa:

Gaya / atomKolomModul
subtask (gaya Pi0.7 “bagaimana, bukan apa”)language_persistentplan
plan (awal + penyegaran saat interupsi)language_persistentplan
memory (kompresi gaya MEM)language_persistentplan
task_aug (parafrasa dari tugas)language_persistentplan
interjectionlanguage_eventsinterjections
atom tool-call ucapan (style=null, say)language_eventsinterjections
vqa (pasangan user / assistant)language_eventsvqa

Cara subtask dibuat

Modul plan tidak meminta subtask dari VLM dalam satu langkah. Sebaliknya, modul ini menggunakan alur describe → segment dua langkah:

  1. Describe — VLM hanya mendeskripsikan apa yang benar-benar terlihat di kamera yang dipilih (tidak menebak-nebak tentang tugasnya).
  2. Segment — deskripsi tersebut dimasukkan kembali, dan VLM membagi episode menjadi subtask atomik yang berurutan.

Kedua pass melihat episode sebagai lembar kontak bertanda waktu — frame yang disampel pada frames_per_second (0,5s secara default) dan dikemas ke dalam grid JPEG dengan waktu setiap frame dicetak di sudutnya, sehingga VLM dapat mengutip waktu batas yang tepat secara langsung. Hal ini jauh lebih hemat dalam token visual dibandingkan satu gambar per frame, sehingga sampling dapat tetap padat; episode yang lebih panjang dari max_frames_per_prompt dipecah menjadi jendela pada kepadatan yang sama dan digabungkan. Kedua prompt juga membawa definisi batas peristiwa yang kausal (peristiwa baru dimulai ketika suatu objek dipegang / dilepaskan / mencapai lokasi baru / penutup berubah status / isi bergerak) untuk memperjelas di mana pemotongan dilakukan.

Secara opsional, jalur ketiga seeded-relabel (--plan.subtask_seeded_relabel) mengunjungi ulang setiap span dengan contact sheet segmen sebelumnya/saat ini/berikutnya dan melakukan koreksi minimal pada label, menggunakan label pertama sebagai prior — batas tetap dipertahankan dan hanya peristilahan yang diperjelas, dengan biaya satu panggilan tambahan per subtask.

Span yang dihasilkan kemudian dirangkai menjadi cakupan episode penuh tanpa celah, sehingga setiap frame memiliki tepat satu subtask aktif. Lihat Running on Hugging Face Jobs untuk pengaturan produksi (kamera tunggal, contact sheet bertanda waktu, generasi subtask dengan jendela otomatis).

Alat

Penulis tidak menambahkan kolom tools ke parquet. Katalog alat berada di meta/info.json["tools"] (lihat Tools). Setelah setiap eksekusi, pipeline memastikan skema say kanonik ada dalam daftar tersebut, sambil mempertahankan alat apa pun yang Anda deklarasikan sebelumnya.

Ingin menambahkan alat Anda sendiri? Edit meta/info.json["tools"] secara langsung — pipeline akan mempertahankan apa pun yang sudah ada. Hal ini membuat alat tersebut terlihat oleh chat template, sehingga model dapat belajar untuk menghasilkan panggilan. Lapisan runtime yang benar-benar mengeksekusi panggilan yang dihasilkan (protokol Tool / TOOL_REGISTRY di bawah src/lerobot/tools/) belum diimplementasikan — dokumen Tools menandai bagian-bagian tersebut sebagai belum diimplementasikan.

Menjalankan di Hugging Face Jobs

Anotasi dataset sungguhan membutuhkan GPU yang cukup besar untuk menjalankan VLM, sehingga lerobot-annotate dapat mengirim dirinya ke Hugging Face Jobs — sama seperti lerobot-train. Tambahkan --job.target=<flavor> ke perintah persis yang akan Anda jalankan secara lokal, dan perintah tersebut akan berjalan di perangkat keras tersebut:

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

Itu mengirimkan tugas h200 GPU tunggal yang:

  1. dimulai dari image vllm/vllm-openai dan menginstal lerobot di atasnya,
  2. menjalankan satu server vLLM per GPU dan mengendalikannya melalui API yang kompatibel dengan OpenAI,
  3. menjalankan modul plan / interjections / vqa di seluruh dataset,
  4. dengan --push_to_hub=true, mengunggah hasil ke --new_repo_id (atau kembali ke --repo_id di tempatnya jika Anda tidak mengatur itu).

Perintah ini mengalirkan log pekerjaan; Ctrl-C memisahkan koneksi tanpa membatalkannya. Daftarkan flavor yang tersedia beserta harganya dengan hf jobs hardware.

Qwen3.6 dirilis dengan fitur berpikir yang diaktifkan, yang menghabiskan anggaran token yang dibutuhkan anotator untuk jawaban JSON-nya — --vlm.chat_template_kwargs='{"enable_thinking": false}' menonaktifkannya. Tanpa --push_to_hub=true, dataset yang telah dianotasi akan dibuang saat pod keluar.

Opsi pekerjaan

BenderaDefaultFungsinya
--job.targetlocalFlavor HF Jobs untuk dijalankan (misalnya h200, h200x4). Dikurangi/local berjalan di sini.
--job.imagevllm/vllm-openai:latestGambar runtime untuk pod.
--job.timeout2hBatas waktu nyata. Naikkan untuk dataset besar.
--job.detachfalseKirim dan keluar alih-alih streaming log.
--job.lerobot_refmainGit ref dari lerobot yang diinstal pada pod — arahkan ke cabang untuk menguji perubahan.
--job.tags[]Tag tambahan pada job dan pada dataset apa pun yang didorongnya (lerobot selalu ditambahkan).

Untuk dataset yang lebih besar, skala ke h200x4 dan naikkan --vlm.parallel_servers / --vlm.num_gpus agar sesuai, serta berikan ruang lebih untuk pekerjaan tersebut, misalnya dengan --job.timeout=8h.

Eksekusi jarak jauh memerlukan --repo_id (pod menarik dataset dari Hub; --root hanya menamai direktori yang ada di mesin Anda). Dataset yang hanya ada di cache lokal Anda akan didorong terlebih dahulu ke repo private.

Opsi utama

Ini adalah flag yang paling sering Anda gunakan. Jalankan lerobot-annotate --help untuk opsi lainnya; nilai defaultnya disesuaikan untuk episode manipulasi yang singkat.

Dataset masuk / keluar

BenderaDefaultFungsinya
--repo_id—Dataset Hub untuk anotasi (diunduh jika --root tidak disetel).
--root—Anotasi direktori dataset lokal sebagai gantinya.
--new_repo_id—Dorong hasil ke repo baru (membiarkan repo sumber tetap utuh).
--push_to_hubfalseUnggah setelah anotasi (ke --new_repo_id, jika tidak kembali ke --repo_id).
--only_episodessemuaAnotasi hanya indeks episode ini (berguna untuk uji coba).
--seed1729Menetapkan benih untuk RNG yang memilih timestamp interupsi + jenis pertanyaan VQA.

Modul mana yang dijalankan

Setiap modul aktif secara default dan dapat diaktifkan/dinonaktifkan secara independen (atur ke false untuk melewatinya, misalnya untuk mengiterasi satu modul pada satu waktu):

BenderaDefaultMatikan
--plan.enabledtruesubtasks + rencana + memori + task_aug
--interjections.enabledtrueinterjeksi + atom ucapan
--vqa.enabledtruepasangan VQA

VLM ( --vlm.* )

BenderaDefaultFungsinya
--vlm.model_idQwen/Qwen3.6-27BModel yang akan disajikan dan prompt.
--vlm.camera_keypertama images.*Kamera mana yang menjadi dasar setiap prompt.
--vlm.serve_commandautoPerintah vllm serve … yang tepat (atur ukuran TP, memori GPU, --max-model-len di sini).
--vlm.parallel_servers1Server independen untuk perutean round-robin (satu per GPU).
--vlm.num_gpus0GPU per server (0 = satu per server).
--vlm.client_concurrency16Permintaan in-flight di semua server.
--vlm.max_new_tokens512Batas generasi per panggilan.
--vlm.temperature0.2Suhu sampling.
--vlm.reasoning_effortnullPetunjuk anggaran berpikir (low/medium/high) diteruskan ke server yang kompatibel dengan OpenAI.

Subtugas / rencana / memori ( --plan.* )

BenderaDefaultFungsinya
--plan.frames_per_second2.0Laju pengambilan sampel frame untuk contact sheets (2.0 = satu frame setiap 0,5 detik).
--plan.max_frames_per_prompt60Anggaran frame per panggilan VLM. Episode yang sampling-nya melebihi ini akan otomatis di-window pada kepadatan yang sama, kemudian di-stitch.
--plan.contact_sheet_columns5Kolom per grid contact-sheet (contact_sheet_frames_per_sheet ubin, baris waktu utama).
--plan.plan_max_steps8Batas atas subtask per episode.
--plan.subtask_describe_firsttrueJalankan jalur grounding describe→segment (kualitas subtask terbaik; +1 panggilan/episode).
--plan.subtask_seeded_relabelfalseLewatan kedua: beri label ulang setiap subtask dari lembar kontak prev/current/next-nya, diinisiasi dengan label pertama (+1 panggilan/subtask).
--plan.subtask_relabel_frames5Frame yang disampel secara seragam per lembar segmen pada jalur relabel (hanya digunakan ketika subtask_seeded_relabel=true).
--plan.emit_plantrueKeluarkan baris plan bernomor (false = hanya subtask + memori).
--plan.emit_memorytrueKeluarkan baris memory (false = hanya subtask + rencana); simetris dengan emit_plan.
--plan.n_task_rephrasings10Berapa banyak rephrasing task_aug yang akan dikeluarkan (0 untuk menonaktifkan).
--plan.derive_task_from_videoif_shortGunakan tugas dataset apa adanya (off), hanya saat hilang/pendek (if_short), atau selalu turunkan ulang dari video (always).

Interjeksi + VQA

BenderaDefaultFungsinya
--interjections.max_interjections_per_episode3Batas pasangan interupsi/ucapan per episode.
--vqa.vqa_emission_hz1.0Seberapa sering pasangan VQA dipancarkan.
--vqa.restrict_to_default_camerafalseGround VQA hanya pada --vlm.camera_key (jika tidak, semua kamera).
--vqa.coordinate_scale1000Grid bbox/keypoint VLM, disimpan sebagai [0, 1] (none = px).
--executor.episode_parallelism16Episodes yang diproses secara bersamaan dalam setiap fase.

Kontribusi modul baru

Pipeline ini dirancang untuk berkembang, dan kontribusi sangat diterima — modul baru (misalnya, jejak lintasan atau affordances), template prompt baru, alur grounding yang lebih cerdas, atau perbaikan kualitas pada modul plan / interjections / vqa yang sudah ada.

Setiap modul berada di bawah src/lerobot/annotations/steerable_pipeline/modules/, berbagi klien VLM dan cache keyframe, menulis output mentahnya ke pohon staging, dan terhubung ke eksekutor sebagai fase tersendiri. Punya ide? Buka issue atau PR di repo.

Bagaimana resep mengonsumsi output

Anotasi dimaksudkan untuk dibaca oleh resep (lihat Kolom Bahasa dan Resep). Biasanya:

  • cabang low-level / high-level / memory-update membaca subtask / plan / memory dari language_persistent.
  • cabang interupsi-respons membaca peristiwa interjection beserta atom ucapan yang berpasangan (digabungkan menjadi satu giliran asisten melalui tool_calls_from) dan pembaruan plan yang sesuai pada timestamp yang sama.
  • cabang VQA membaca pasangan (vqa, user) dan (vqa, assistant) dari language_events.

Mengapa state dan events dipisahkan

Dua gagasan membentuk desainnya:

  1. State persisten vs. event yang tepat. Baris persisten (subtask, plan, memory) berlaku untuk seluruh episode dan menjawab “apa yang benar saat ini?”. Baris event (interjection, vqa, ucapan) hanya muncul pada satu frame yang timestamp-nya cocok. Timestamp disalin langsung dari sumber parquet — tidak pernah dihitung ulang dalam floating point.
  2. Satu kali lintasan VLM. Ketiga modul berbagi satu klien VLM (klien yang kompatibel dengan OpenAI yang berkomunikasi dengan server vLLM dari job tersebut), sehingga Anda hanya membayar untuk satu pemuatan model per dataset, bukan tiga.

Menjalankan ulang satu modul

Setiap modul menaruh output mentahnya ke <root>/.annotate_staging/episode_{N:06d}/<module>.jsonl. Hal ini membuat iterasi prompt menjadi murah: menjalankan ulang satu modul hanya menimpa JSONL miliknya sendiri, lalu writer menyusun ulang parquet final. Nonaktifkan modul yang tidak Anda inginkan dengan --plan.enabled=false (dan juga --interjections.enabled / --vqa.enabled) untuk menguji satu per satu.

Yang diperiksa oleh validator

Sebelum writer dijalankan, StagingValidator memastikan:

  • setiap baris event mendarat tepat pada timestamp frame yang sebenarnya;
  • tidak ada pasangan ucapan / interjeksi yang tersisa tanpa pasangan;
  • plan diperbarui pada setiap timestamp interupsi;
  • baris memory jatuh pada batas subtask (peringatan, bukan kesalahan);
  • setiap content asisten VQA adalah JSON valid dalam salah satu bentuk bbox / keypoint / count / attribute / spatial;
  • setiap baris masuk ke kolom yang dipilih oleh column_for_style(style).

Setiap kesalahan akan menghentikan writer. Lewati --skip_validation=true untuk menimpa perilaku ini saat debugging.

Dari mana ide setiap modul berasal

  • plan — subtask. Hi Robot (Shi 2025) untuk granularitas atom (“ambil satu helai selada”, “letakkan mangkuk ke kotak”); Pi0.7 (Physical Intelligence 2025) untuk detail “bagaimana, bukan apa”.
  • plan — memori. MEM (Torne 2026): pertahankan hanya informasi relevan yang minimal — simpan hasil, buang atribut spesifik.
  • interjections. Taksonomi skenario Hi Robot: tugas negatif, koreksi situasional, kendala spesifik, preferensi. Bicara adalah atom yang hanya berupa pemanggilan alat (tool_calls=[{type:function, function:{name:"say", arguments:{text:...}}}]).
  • vqa. ECoT (Zawalski 2024) untuk fitur yang berakar pada data (bounding box [x_min, y_min, x_max, y_max] dan keypoints, disimpan sebagai fraksi gambar [0, 1]) dan Steerable VLA Policies (Zhao 2025) untuk grounding multi-abstraksi. Pi0.7 juga mengaitkan jawaban di berbagai tingkat abstraksi.

Saat meningkatkan modul, sesuaikan template prompt-nya di src/lerobot/annotations/steerable_pipeline/prompts/ alih-alih menulis ulang dari awal.

Kira-kira berapa biayanya

Per episode, pipeline melakukan sekitar plan_max_steps panggilan plan, max_interjections_per_episode panggilan interjection, dan vqa_emission_hz × episode_seconds panggilan VQA. Dengan nilai default (8 subtask, 3 interjection, 1 Hz × 3 pasangan) pada episode 30 detik, itu sekitar ~50 panggilan VLM.

Penyimpanan tetap kecil: language_persistent maksimal puluhan KB per episode (parquet melakukan dictionary-encoding pada satu entri yang berulang di seluruh frame), dan language_events kosong di sebagian besar frame — ukurannya berskala dengan jumlah emisi, bukan num_frames × num_emissions.

Update on GitHub