LeRobot documentation

アノテーションパイプライン

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

アノテーションパイプライン

lerobot-annotate は、ビジョン言語モデル (VLM) を用いて各エピソードの動画を監視し、自然言語によるアノテーションをデータセットに書き戻します。Language Columns and Recipes ページで定義されている 2 つの言語カラム、language_persistent と language_events に値を埋め込み、data/chunk-*/file-*.parquet に直接反映します。

要するに、LeRobotデータセットを指定するだけで、ポリシーの学習に使用できるサブタスク、計画、メモリ、割り込み、音声、視覚Q&Aが追加されます。

全体像

  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)

3つのモジュール(plan、interjections、vqa)はすべて、1つの共有VLMと通信します。各モジュールは出力をディスクにステージングし、バリデータがそれを検証し、単一のライターがデータセットのシャードをその場で書き換えます。

パイプラインが生成するもの

各モジュールは、いくつかの種類のアノテーション(「スタイル」)を出力し、それらは2つの言語カラムのいずれかにルーティングされます。

スタイル / 原子カラムモジュール
subtask(Pi0.7スタイルの「何をではなく、どうやって」)language_persistentplan
plan(初期化と割り込み時の更新)language_persistentplan
memory(MEMスタイルの圧縮)language_persistentplan
task_aug(タスクの言い換え)language_persistentplan
interjectionlanguage_eventsinterjections
speech tool-call atom (style=null、say)language_eventsinterjections
vqa(ユーザー / アシスタントのペア)language_eventsvqa

サブタスクの生成方法

planモジュールは、VLMにサブタスクを一度に要求しません。代わりに、describe → segmentの2段階のフローを使用します。

  1. Describe — VLMは選択したカメラで実際に確認できる内容のみを説明します(タスクに関する推測は行いません)。
  2. Segment — その説明がフィードバックされ、VLM がエピソードを連続するアトミックなサブタスクに分割します。

両方のパスは、エピソードをタイムスタンプ付きのコンタクトシートとして認識します。これはframes_per_second(デフォルトは0.5秒)でサンプリングされたフレームをJPEGグリッドにパックし、各フレームの隅にその時刻を焼き込んだものです。これにより、VLMは境界の正確な時刻を直接引用できます。1フレーム1画像に比べ、ビジョントークンの消費が大幅に少なくなるため、サンプリングを高密度に保つことができます。max_frames_per_promptを超える長さのエピソードは、同じ密度でウィンドウに分割され、マージされます。また、両方のプロンプトには、カットの位置を明確にするための因果的なイベント境界の定義(オブジェクトが保持される/解放される/新しい位置に到達する/蓋の状態が変わる/内容物が移動するときに新しいイベントが始まる)が含まれています。

任意で、3番目のseeded-relabelパス(--plan.subtask_seeded_relabel)を実行すると、各スパンについて、その前/現在/次のセグメントのコンタクトシートを参照しながら、最初のラベルを事前情報として用いてラベルを最小限に修正します。この処理では境界は固定され、表現の明確化のみが行われますが、サブタスクごとに1回の追加呼び出しが発生します。

生成されたスパンは、ギャップのないエピソード全体のカバーに縫い合わされ、すべてのフレームにちょうど1つのアクティブなサブタスクが割り当てられます。本番環境の設定(単一カメラ、タイムスタンプ付きコンタクトシート、自動ウィンドウ化されたサブタスク生成)については、Hugging Face Jobsでの実行を参照してください。

ツール

writer は parquet に tools 列を追加しません。ツールカタログは代わりに meta/info.json["tools"] に格納されています(Tools を参照)。各実行のたびに、パイプラインは正規の say スキーマがそのリストに含まれていることを確認し、事前に宣言したツールを保持します。

独自のツールを追加したい場合は、meta/info.json["tools"]を直接編集してください。パイプラインは、すでに存在する内容を保持します。これによりツールがチャットテンプレートに公開され、モデルが呼び出しを生成することを学習できます。生成された呼び出しを実際に実行するランタイムレイヤー(src/lerobot/tools/配下のToolプロトコル / TOOL_REGISTRY)はまだ実装されていません。Toolsドキュメントでは、これらの部分が未実装と明記されています。

Hugging Face Jobsでの実行

実際のデータセットのアノテーションには、VLMをサーブするのに十分な大きさのGPUが必要です。そのため、lerobot-annotateはlerobot-trainと同様に、Hugging Face Jobsに自身をディスパッチできます。ローカルで実行する予定の正確なコマンドに--job.target=<flavor>を追加すると、代わりにそのハードウェア上で実行されます:

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

単一GPUのh200ジョブを送信します。このジョブは以下を実行します:

  1. vllm/vllm-openaiイメージを起点として、その上にlerobotをインストールします。
  2. GPUごとに1つのvLLMサーバーを起動し、OpenAI互換APIを介して操作します。
  3. データセット全体でplan / interjections / vqaモジュールを実行します。
  4. --push_to_hub=true を指定すると、結果を --new_repo_id にアップロードします(--repo_id を設定しない場合、元の --repo_id に上書きしてアップロードします)。

このコマンドはジョブのログをストリーミング表示します。Ctrl-Cでキャンセルせずにデタッチできます。hf jobs hardwareで利用可能なフレーバーとその価格一覧を表示できます。

Qwen3.6はデフォルトでthinkingが有効になっていますが、これによりアノテーターがJSONの回答に必要とするトークン予算を消費してしまいます。--vlm.chat_template_kwargs='{"enable_thinking": false}'を指定することでこれを無効にできます。--push_to_hub=trueを指定しないと、podが終了した際にアノテーション済みデータセットが破棄されます。

ジョブのオプション

フラグデフォルト機能
--job.targetlocal実行するHF Jobsのflavor(例: h200、h200x4)。省略またはlocalの場合は、ここで実行されます。
--job.imagevllm/vllm-openai:latestPodのランタイムイメージ。
--job.timeout2h実時間上限。大規模なデータセットの場合は、これを引き上げてください。
--job.detachfalseログをストリーミングする代わりに、送信して終了します。
--job.lerobot_refmainPodにインストールされているlerobotのGit ref — 変更をテストするためにブランチを指定します。
--job.tags[]ジョブおよびそのジョブがプッシュするすべてのデータセットに付与される追加のタグ(lerobotは常に追加されます)。

より大きなデータセットを使用する場合は、h200x4 にスケールアップし、--vlm.parallel_servers / --vlm.num_gpus をそれに合わせて引き上げ、例えば --job.timeout=8h のようにしてジョブに余裕を持たせてください。

リモート実行には--repo_idが必要です(ポッドはHubからデータセットを取得します。--rootは、あなたのマシンにのみ存在するディレクトリを指定します)。ローカルキャッシュにのみ存在するデータセットは、まずprivateリポジトリにプッシュされます。

主要なオプション

これらは最も頻繁に使用するフラグです。その他のオプションについてはlerobot-annotate --helpを実行してください。デフォルト値は、短い操作エピソード用に調整されています。

入出力用データセット

フラグデフォルト機能
--repo_id—アノテーション対象のHubデータセット(--rootが設定されていない場合はダウンロードされます)。
--root—ローカルのデータセットディレクトリをアノテーションします。
--new_repo_id—結果を新しいリポジトリにプッシュします(ソースリポジトリは変更されません)。
--push_to_hubfalseアノテーション後にアップロードします(--new_repo_idに、それ以外の場合は--repo_idに)。
--only_episodesすべてこれらのエピソードのインデックスのみをアノテーションします(テスト実行に便利です)。
--seed1729割り込みのタイムスタンプとVQAの質問タイプを選択するRNGをシードします。

どのモジュールが実行されるか

すべてのモジュールはデフォルトで有効になっており、独立して切り替えることができます(falseに設定するとスキップされます。例えば、モジュールを1つずつ反復処理する場合など):

フラグデフォルトオフにします
--plan.enabledtrueサブタスク + プラン + メモリ + task_aug
--interjections.enabledtrue間投詞 + 発話アトム
--vqa.enabledtrueVQAペア

VLM(--vlm.*)

フラグデフォルト機能
--vlm.model_idQwen/Qwen3.6-27Bサーブするモデルとプロンプト。
--vlm.camera_keyimages.* 最初各プロンプトが基づくカメラ。
--vlm.serve_commandauto正確なvllm serve …コマンド(ここでTPサイズ、GPUメモリ、--max-model-lenを設定します)。
--vlm.parallel_servers1ラウンドロビンルーティング用の独立したサーバー(GPUごとに1つ)。
--vlm.num_gpus0サーバーあたりのGPU数(0 = 1台ずつ)。
--vlm.client_concurrency16すべてのサーバーにわたるフライト中のリクエスト
--vlm.max_new_tokens5121回の呼び出しあたりの生成上限。
--vlm.temperature0.2サンプリング温度。
--vlm.reasoning_effortnullOpenAI互換サーバーに転送される思考予算のヒント(low/medium/high)。

サブタスク / プラン / メモリ ( --plan.* )

フラグデフォルト機能
--plan.frames_per_second2.0コンタクトシート用のフレームサンプリングレート(2.0 = 0.5秒ごとに1フレーム)。
--plan.max_frames_per_prompt60VLM呼び出しあたりのフレーム予算。サンプリングがこの値を超えるエピソードは、同じ密度で自動ウィンドウ化され、その後結合されます。
--plan.contact_sheet_columns5コンタクトシートグリッドの列数(contact_sheet_frames_per_sheetタイル、時間方向を先行とする行優先)。
--plan.plan_max_steps8エピソードあたりのサブタスクの上限。
--plan.subtask_describe_firsttruedescribe→segment グラウンディングパスを実行します(サブタスクの品質が最も高く、エピソードあたり+1回の呼び出し)。
--plan.subtask_seeded_relabelfalse2回目のパス: 各サブタスクを、その前/現在/次のコンタクトシートから、最初のラベルをシードとして再ラベル付けします(+1コール/サブタスク)。
--plan.subtask_relabel_frames5リレーベルパスの各セグメントシートで均等にサンプリングされるフレーム数(subtask_seeded_relabel=trueの場合のみ使用されます)。
--plan.emit_plantrueplan の番号付き行を出力します(false = サブタスクとメモリのみ)。
--plan.emit_memorytruememory行を出力します(false = サブタスクとプランのみ)。emit_planと対称です。
--plan.n_task_rephrasings10task_aug の言い換えを何件出力するか(0 で無効化)。
--plan.derive_task_from_videoif_shortデータセットのタスクをそのまま使用する(off)、欠落または短い場合のみ使用する(if_short)、または常に動画から再導出する(always)。

間投詞 + VQA

フラグデフォルト機能
--interjections.max_interjections_per_episode3エピソードごとの割り込み/発話ペアの上限。
--vqa.vqa_emission_hz1.0VQAペアが出力される頻度。
--vqa.restrict_to_default_camerafalse--vlm.camera_key のカメラのみで VQA を実行します(指定しない場合はすべてのカメラを使用します)。
--vqa.coordinate_scale1000VLMのbbox/keypointグリッド。[0, 1]で保存されます(none = px)。
--executor.episode_parallelism16各フェーズ内で並列処理されるエピソード数。

新しいモジュールの貢献

このパイプラインは拡張を前提として構築されており、貢献を大歓迎しています。例えば、トラジェクトリー・トレースやアフォーダンスといった全く新しいモジュール、新しいプロンプト・テンプレート、より賢いグラウンディング・フロー、あるいは既存のplan / interjections / vqaモジュールの品質改善などです。

すべてのモジュールはsrc/lerobot/annotations/steerable_pipeline/modules/の下に配置され、VLMクライアントとキーフレームキャッシュを共有し、生出力をステージングツリーに書き込み、独自のフェーズとしてエグゼキュータに組み込まれます。アイデアがあれば、リポジトリでissueまたはPRを作成してください。

レシピが出力を消費する方法

アノテーションはレシピによって読み取られることを想定しています(言語カラムとレシピを参照)。一般的には、以下のようになります。

  • low-level / high-level / memory-update ブランチは、language_persistent から subtask / plan / memory を読み取ります。
  • 中断応答ブランチは、interjectionイベントと、対応する発話アトム(tool_calls_fromを介して1つのアシスタントターンにマージされる)および、同じタイムスタンプの対応するplanリフレッシュを読み取ります。
  • VQAブランチはlanguage_eventsから(vqa, user)および(vqa, assistant)のペアを読み取ります。

ステートとイベントが分離されている理由

設計を形作る2つの考え:

  1. 永続状態と正確なイベント。 永続行(subtask、plan、memory)はエピソード全体に適用され、「今まさに真実であることは何か?」という問いに答えます。イベント行(interjection、vqa、発話)は、タイムスタンプが一致する1フレームのみに表示されます。タイムスタンプはソースの parquet からそのままコピーされ、浮動小数点で再計算されることはありません。
  2. VLMパスは1回のみ。 3つのモジュールはすべて単一のVLMクライアント(ジョブのvLLMサーバーと通信するOpenAI互換クライアント)を共有するため、データセットごとにモデルの読み込みコストは1回分のみで済みます。

単一モジュールの再実行

各モジュールは、生の出力を<root>/.annotate_staging/episode_{N:06d}/<module>.jsonlにステージングします。これにより、プロンプトの反復が低コストになります。1つのモジュールを再実行すると、そのモジュール自身のJSONLのみが上書きされ、その後、ライターが最終的なparquetを再構成します。テストを1つずつ行うには、不要なモジュールを--plan.enabled=false(同様に--interjections.enabled / --vqa.enabled)で無効にしてください。

バリデータがチェックする内容

ライターの実行前に、StagingValidatorが以下を確認します:

  • すべてのイベント行が実際のフレームのタイムスタンプに正確に一致します。
  • 孤立した発話/間投詞のペアが残っていないこと;
  • plan は、各割り込みタイムスタンプで更新されます。
  • memory の行がサブタスクの境界に位置する場合(警告であり、エラーではありません);
  • 各VQAアシスタントのcontentは、bbox / keypoint / count / attribute / spatial のいずれかの形式で、有効なJSONです。
  • 各行はcolumn_for_style(style)によって選択された列に格納されます。

エラーが発生すると、ライターは中断されます。デバッグ中は--skip_validation=trueを渡して上書きできます。

各モジュールのアイデアの由来

  • plan — サブタスク。 Hi Robot(Shi 2025)はアトム粒度(「レタスの1枚を拾う」、「ボウルを箱に置く」)に対応し、Pi0.7(Physical Intelligence 2025)は「何を」ではなく「どのように」行うかという詳細に対応します。
  • plan — メモリ。 MEM(Torne 2026): 最小限の関連情報のみを保持します — 結果を保持し、具体的な属性は削除します。
  • interjections。 Hi Robot のシナリオ分類体系: negative task、situated correction、specific constraint、preference。発話は tool-call 専用のアトムです (tool_calls=[{type:function, function:{name:"say", arguments:{text:...}}}])。
  • vqa. 根拠付き特徴(バウンディングボックス [x_min, y_min, x_max, y_max] およびキーポイント、[0, 1] の画像比率として保存)には ECoT(Zawalski 2024)、マルチ抽象レベルのグラウンディングには Steerable VLA Policies(Zhao 2025)を使用します。Pi0.7 もまた、抽象レベルをまたいで回答をグラウンディングします。

モジュールを改善する際は、ゼロから書き直すのではなく、src/lerobot/annotations/steerable_pipeline/prompts/内のプロンプトテンプレートを調整してください。

おおよそのコスト

エピソードごとに、パイプラインは約plan_max_steps回のプラン呼び出し、max_interjections_per_episode回の割り込み呼び出し、およびvqa_emission_hz × episode_seconds回のVQA呼び出しを行います。デフォルト設定(サブタスク8、割り込み3、1 Hz × 3ペア)で30秒のエピソードの場合、VLMの呼び出しは約50回になります。

ストレージは小さく保たれます: language_persistent はエピソードあたり最大数十 KB 程度です(parquet はフレーム間で繰り返し出現する 1 つのエントリを辞書符号化します)。また、language_events はほとんどのフレームで空です。そのサイズは num_frames × num_emissions ではなく、エミッションの数に比例して増加します。

Update on GitHub