LeRobot documentation

Annotation Pipeline

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Annotation Pipeline

lerobot-annotate는 각 에피소드의 비디오를 비전-언어 모델(VLM)로 분석하여 자연어 주석을 데이터셋에 기록합니다. Language Columns and Recipes 페이지의 두 언어 열인 language_persistent와 language_events를 data/chunk-*/file-*.parquet에 직접 채워 넣습니다.

요약하면: LeRobot 데이터셋을 지정하면, 정책(policy)을 학습할 수 있는 서브태스크, 계획, 메모리, 삽화, 음성, 시각 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)

세 개의 모듈(plan, interjections, vqa)이 모두 하나의 공유 VLM과 통신합니다. 각 모듈은 출력을 디스크에 스테이징하고, 검증기가 이를 확인한 후, 단일 작성기가 데이터셋 샤드를 원자리에 다시 작성합니다.

파이프라인이 생성하는 것

각 모듈은 몇 가지 유형의 주석(“스타일”)을 출력하며, 이는 두 언어 열 중 하나로 라우팅됩니다:

스타일 / 원자열모듈
subtask(Pi0.7 스타일의 “무엇이 아니라 어떻게”)language_persistentplan
plan (초기 + 중재 시 새로고침)language_persistentplan
memory (MEM 스타일 압축)language_persistentplan
task_aug (작업의 재구성)language_persistentplan
interjectionlanguage_eventsinterjections
음성 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 그리드로 패키징되며, 각 프레임의 시간이 모서리에 굽힘(burn-in) 처리되어 VLM이 정확한 경계 시간을 직접 인용할 수 있습니다. 이는 프레임당 하나의 이미지보다 비전 토큰 비용이 훨씬 저렴하므로 샘플링을 밀도 있게 유지할 수 있으며, max_frames_per_prompt를 초과하는 길이의 에피소드는 동일한 밀도로 윈도우로 분할된 후 병합됩니다. 두 프롬프트 모두 인과적 이벤트 경계 정의(개체가 잡히거나/놓이거나/새로운 위치에 도달하거나/뚜껑 상태가 변경되거나/내용물이 이동할 때 새로운 이벤트가 시작됨)를 포함하여 컷이 위치하는 지점을 명확히 합니다.

선택적으로, 세 번째 seeded-relabel 패스(--plan.subtask_seeded_relabel)는 각 스팬에 대해 이전/현재/다음 세그먼트의 컨택트 시트를 다시 검토하고, 첫 번째 라벨을 사전 지식으로 사용하여 라벨을 최소한으로 수정합니다. 이 과정에서 경계는 고정된 상태로 유지되며, 서브태스크당 호출이 한 번 추가되는 비용으로 표현만 더 정확하게 다듬습니다.

결과 스팬은 이어짐이 없는 전체 에피소드 커버로 이어지며, 모든 프레임에 정확히 하나의 활성 서브태스크가 할당됩니다. 프로덕션 설정(단일 카메라, 타임스탬프가 있는 컨택트 시트, 자동 윈도우 기반 서브태스크 생성)은 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는 Hugging Face Jobs로 자신을 디스패치할 수 있습니다. lerobot-train와 동일한 방식입니다. 로컬에서 실행할 정확한 명령에 --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당 vLLM 서버를 하나씩 시작하고 OpenAI 호환 API를 통해 제어합니다.
  3. 데이터셋 전체에서 plan / interjections / vqa 모듈을 실행합니다.
  4. --push_to_hub=true를 사용하면 결과를 --new_repo_id에 업로드합니다(설정하지 않으면 --repo_id에 그대로 업로드됩니다).

이 명령은 작업의 로그를 스트리밍합니다. Ctrl-C를 누르면 취소하지 않고 디스택됩니다. hf jobs hardware를 사용하여 사용 가능한 flavor 목록과 가격을 확인하세요.

Qwen3.6은 기본적으로 thinking이 활성화된 상태로 제공되며, 이는 annotator가 JSON 답변에 필요한 토큰 예산을 소모합니다. --vlm.chat_template_kwargs='{"enable_thinking": false}'를 사용하면 이를 비활성화할 수 있습니다. --push_to_hub=true 옵션이 없으면 pod이 종료될 때 annotator가 생성한 데이터셋이 폐기됩니다.

작업 옵션

플래그기본값기능
--job.targetlocal실행할 HF Jobs flavor(예: h200, h200x4). 생략 또는 local이면 여기서 실행됩니다.
--job.imagevllm/vllm-openai:latestPod의 런타임 이미지입니다.
--job.timeout2h실시간 제한. 대규모 데이터셋의 경우 이 값을 높여야 합니다.
--job.detachfalse로그 스트리밍 대신 제출하고 종료합니다.
--job.lerobot_refmain에 설치된 의 Git ref — 변경 사항을 테스트하려면 브랜치를 지정하세요.
--job.tags[]및 해당 이 푸시하는 모든 에 추가 태그(lerobot는 항상 추가됩니다).

더 큰 데이터셋의 경우, h200x4로 확장하고 --vlm.parallel_servers / --vlm.num_gpus를 그에 맞게 높이며, 예를 들어 --job.timeout=8h를 사용하여 작업에 더 많은 여유를 확보하세요.

Remote 실행에는 --repo_id가 필요합니다(pod가 Hub에서 데이터를 가져오기 때문이며, --root는 로컬 머신에만 존재하는 디렉토리를 지정합니다). 로컬 캐시에만 존재하는 데이터셋은 먼저 private 저장소에 push됩니다.

주요 옵션

가장 자주 사용하는 플래그입니다. 그 외의 모든 항목은 lerobot-annotate --help를 실행하여 확인하세요. 기본값은 짧은 조작 에피소드에 맞게 조정되어 있습니다.

데이터셋 입출력

플래그기본값기능
--repo_id—--root이 설정되지 않은 경우 다운로드되는) 주석을 달 Hub 데이터셋.
--root—대신 로컬 데이터셋 디렉토리를 어노테이션하세요.
--new_repo_id—결과를 새 저장소에 푸시합니다(소스 저장소는 그대로 유지됩니다).
--push_to_hubfalse--new_repo_id로, 그렇지 않으면 --repo_id로 업로드
--only_episodes전체이 에피소드 인덱스만 주석 처리합니다(테스트 실행에 유용합니다).
--seed1729Seeds는 중간 삽입 타임스탬프와 VQA 질문 유형을 선택하는 RNG를 초기화합니다.

어떤 모듈이 실행되는지

모든 모듈은 기본적으로 활성화되어 있으며 독립적으로 전환할 수 있습니다(예: 한 번에 하나의 모듈만 반복하려면 false로 설정하여 해당 모듈을 건너뜁니다):

플래그기본값끄기
--plan.enabledtruesubtasks + plan + memory + task_aug
--interjections.enabledtrue감탄사 + 화용 원소
--vqa.enabledtrueVQA 쌍

VLM( --vlm.* )

플래그기본값기능
--vlm.model_idQwen/Qwen3.6-27B서빙할 모델과 프롬프트입니다.
--vlm.camera_keyimages.*의 첫 번째각 프롬프트가 기반을 두는 카메라입니다.
--vlm.serve_commandautovllm serve … 명령어(여기서 TP 크기, GPU 메모리, --max-model-len 설정)
--vlm.parallel_servers1라운드 로빈 라우팅을 위한 독립적인 서버(각 GPU당 하나).
--vlm.num_gpus0서버당 GPU 수(0 = 각각 하나).
--vlm.client_concurrency16모든 서버에 걸친 진행 중인 요청.
--vlm.max_new_tokens512호출당 생성 한도.
--vlm.temperature0.2샘플링 온도.
--vlm.reasoning_effortnullOpenAI 호환 서버로 전달되는 사고 예산 힌트(low/medium/high).

하위 작업 / 계획 / 메모리 ( --plan.* )

플래그기본값기능
--plan.frames_per_second2.0컨택트 시트의 프레임 샘플링 속도(2.0 = 0.5초마다 1프레임).
--plan.max_frames_per_prompt60각 VLM 호출당 프레임 예산. 이 값을 초과하여 샘플링되는 에피소드는 동일한 밀도로 자동 윈도우 처리된 후 스티칭됩니다.
--plan.contact_sheet_columns5컨택트 시트 그리드당 열 수 (contact_sheet_frames_per_sheet 타일, 시간 행 우선).
--plan.plan_max_steps8에피소드당 하위 작업 수의 상한.
--plan.subtask_describe_firsttruedescribe→segment grounding 패스를 실행합니다(최상의 하위 작업 품질; 에피소드당 호출 +1회).
--plan.subtask_seeded_relabelfalse두 번째 패스: 각 하위 작업에 대해 이전/현재/다음 컨택트 시트를 기반으로, 첫 번째 라벨을 시드로 사용하여 다시 라벨링합니다(하위 작업당 +1 호출).
--plan.subtask_relabel_frames5subtask_seeded_relabel=true일 때만 사용되는 relabel pass에서 각 segment sheet마다 균등하게 샘플링된 프레임 수
--plan.emit_plantrue번호가 매겨진 plan 행을 출력합니다(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각 단계 내에서 병렬로 처리되는 에피소드 수.

새 모듈 기여

이 파이프라인은 확장되도록 설계되어 있으며 기여를 매우 환영합니다 — 완전히 새로운 모듈(예: trajectory traces 또는 affordances), 새로운 프롬프트 템플릿, 더 지능적인 grounding 플로우, 또는 기존 plan / interjections / vqa 모듈에 대한 품질 개선 등이 해당됩니다.

모든 모듈은 src/lerobot/annotations/steerable_pipeline/modules/ 아래에 위치하며, VLM 클라이언트와 keyframe 캐시를 공유하고, 원시 출력을 스테이징 트리에 기록하며, 실행기에 자체 페이즈로 연결됩니다. 아이디어가 있으신가요? 레포지토리에 이슈나 PR을 열어 주세요.

레시피가 출력을 소비하는 방식

주석은 레시피가 읽도록 작성됩니다(자세한 내용은 Language Columns and Recipes를 참조). 일반적으로:

  • low-level / high-level / memory-update 브랜치는 language_persistent에서 subtask / plan / memory를 읽습니다.
  • interjection-response branch는 interjection 이벤트와 짝을 이룬 speech atom(tool_calls_from을 통해 하나의 assistant turn으로 병합됨) 및 동일한 타임스탬프의 매칭되는 plan refresh를 읽습니다.
  • VQA 브랜치는 language_events에서 (vqa, user) 및 (vqa, assistant) 쌍을 읽습니다.

상태와 이벤트가 분리되는 이유

설계를 형성하는 두 가지 아이디어가 있습니다:

  1. 영속 상태와 정확한 이벤트. 영속 행(subtask, plan, memory)은 에피소드 전체에 적용되며 “지금 무엇이 참인가?”라는 질문에 답합니다. 이벤트 행(interjection, vqa, 음성)은 타임스탬프가 일치하는 프레임에서만 나타납니다. 타임스탬프는 소스 parquet에서 그대로 복사되며, 부동 소수점으로 다시 계산하지 않습니다.
  2. VLM 패스 1회. 세 모듈 모두 단일 VLM 클라이언트(작업의 vLLM 서버와 통신하는 OpenAI 호환 클라이언트)를 공유하므로, 세 번이 아닌 데이터셋당 모델 로드 비용이 1회만 발생합니다.

단일 모듈 재실행

각 모듈은 원시 출력을 <root>/.annotate_staging/episode_{N:06d}/<module>.jsonl에 스테이징합니다. 이를 통해 프롬프트 반복 작업의 비용이 낮아집니다. 하나의 모듈을 다시 실행하면 해당 모듈의 JSONL만 덮어쓰고, 이후 writer가 최종 parquet을 다시 구성합니다. --plan.enabled=false(그리고 마찬가지로 --interjections.enabled / --vqa.enabled)를 사용하여 원하지 않는 모듈을 비활성화하고 하나씩 테스트할 수 있습니다.

검증기가 검사하는 항목

writer가 실행되기 전에, StagingValidator는 다음을 확인합니다:

  • 모든 이벤트 행이 실제 프레임 타임스탬프에 정확히 매핑됩니다;
  • no speech / interjection pairs are left orphaned;
  • plan은 모든 interjection 타임스탬프에서 새로고침됩니다;
  • memory 행이 서브태스크 경계에 위치합니다(경고이며 오류가 아닙니다).
  • 각 VQA 어시스턴트 content은 bbox / keypoint / count / attribute / spatial 형식 중 하나인 유효한 JSON입니다.
  • 각 행은 column_for_style(style)에 의해 선택된 열로 이동합니다.

오류가 발생하면 writer가 중단됩니다. 디버깅 중에는 --skip_validation=true를 전달하여 이 동작을 무시할 수 있습니다.

각 모듈의 아이디어 출처

  • plan — 하위 작업. Hi Robot (Shi 2025)는 원자 단위 세분화(“양배추 한 조각 집기”, “그릇을 상자에 놓기”)에, Pi0.7 (Physical Intelligence 2025)는 “무엇이 아니라 어떻게”라는 세부 사항에 초점을 맞춥니다.
  • plan — 메모리. MEM(Torne 2026): 최소한의 관련 정보만 유지하세요 — 결과를 보존하고 구체적인 속성은 제거하세요.
  • interjections. Hi Robot의 시나리오 분류 체계: negative task, situated correction, specific constraint, preference. Speech는 tool-call-only 원자입니다 (tool_calls=[{type:function, function:{name:"say", arguments:{text:...}}}]).
  • vqa. grounded features(bounding box [x_min, y_min, x_max, y_max] 및 keypoint, [0, 1] 이미지 비율로 저장)에는 Zawalski 2024의 ECoT를, multi-abstraction grounding에는 Zhao 2025의 Steerable VLA Policies를 사용합니다. Pi0.7도 추상화 수준에 걸쳐 답변을 grounding합니다.

모듈을 개선할 때는 처음부터 다시 작성하는 대신 src/lerobot/annotations/steerable_pipeline/prompts/의 프롬프트 템플릿을 조정하세요.

대략적인 비용

에피소드당 파이프라인은 약 plan_max_steps개의 plan 호출, max_interjections_per_episode개의 interjection 호출, vqa_emission_hz × episode_seconds개의 VQA 호출을 수행합니다. 기본값(8개 서브태스크, 3개 interjection, 1 Hz × 3 쌍)을 30초 에피소드에 적용하면 약 50개의 VLM 호출이 발생합니다.

저장 용량은 작게 유지됩니다: language_persistent는 에피소드당 최대 수십 KB 수준이며(parquet는 프레임 간에 반복되는 하나의 항목을 사전 인코딩합니다), language_events는 대부분의 프레임에서 비어 있습니다. 그 크기는 프레임 수와 발화 수의 곱인 num_frames × num_emissions가 아니라 발화 횟수에 따라 비례합니다.

Update on GitHub