mirror of
https://github.com/huggingface/lerobot.git
synced 2026-08-08 17:39:44 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a56fc0b174 | |||
| 39c4e746f1 | |||
| d3ee0b820c | |||
| 072c697c0e | |||
| 266be2bd17 | |||
| ff7cc3de1d | |||
| 31fedfd9dd | |||
| b1bf24f565 |
@@ -24,24 +24,19 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
# Triggers on pushes to main that touch the docs or the sources the API reference is generated from.
|
||||
# `src/**` is included because the API reference is built from docstrings via `[[autodoc]]`: without it,
|
||||
# published API pages would go stale as soon as a docstring changed.
|
||||
# Triggers the workflow on push events to main for the docs folder
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "src/**"
|
||||
|
||||
# Same for pull requests, so a docstring change gets a preview build and a broken `[[autodoc]]` path
|
||||
# fails the PR rather than main.
|
||||
# Triggers the workflow on pull request events targeting main for the docs folder
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "docs/**"
|
||||
- "src/**"
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
@@ -64,21 +59,12 @@ jobs:
|
||||
with:
|
||||
commit_sha: ${{ github.sha }}
|
||||
package: lerobot
|
||||
# doc-builder ships a mock-deps registry entry for lerobot, so the reusable workflow takes its
|
||||
# "light install" path: `pip install ./lerobot --no-deps` plus a handful of real dependencies.
|
||||
# That is not enough to import lerobot — draccus runs `register_subclass` at import time and
|
||||
# `processor/converters.py` calls `functools.singledispatch.register(torch.Tensor)`, neither of
|
||||
# which works against a mock. Install the package for real before the build.
|
||||
pre_command: uv pip install "./lerobot[dataset]"
|
||||
# `--version main` is load-bearing: without `--not_python_module`, doc-builder falls back to
|
||||
# `lerobot.__version__` and only maps that to the default branch when it contains "dev". Our main
|
||||
# branch carries a release version (0.6.2), so omitting this would publish the main docs to
|
||||
# /lerobot/v0.6.2/ instead of /lerobot/main/ and disable notebook building.
|
||||
additional_args: >-
|
||||
--not_python_module
|
||||
${{
|
||||
(github.event_name == 'release' && format('--version {0}', github.event.release.tag_name)) ||
|
||||
(inputs.version != '' && format('--version {0}', inputs.version)) ||
|
||||
'--version main'
|
||||
''
|
||||
}}
|
||||
secrets:
|
||||
token: ${{ secrets.HUGGINGFACE_PUSH }}
|
||||
@@ -97,6 +83,4 @@ jobs:
|
||||
commit_sha: ${{ github.event.pull_request.head.sha }}
|
||||
pr_number: ${{ github.event.number }}
|
||||
package: lerobot
|
||||
# See the comment on build_main_docs. The PR workflow passes its own `--version pr_<n>`, so no
|
||||
# additional_args are needed here.
|
||||
pre_command: uv pip install "./lerobot[dataset]"
|
||||
additional_args: --not_python_module
|
||||
|
||||
@@ -56,41 +56,3 @@ jobs:
|
||||
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
|
||||
with:
|
||||
extra_args: --all-files --show-diff-on-failure --color=always
|
||||
|
||||
# This job runs the examples in our docstrings and validates the doctest allowlist.
|
||||
# See docs/source/writing_docstrings.mdx for the standard these enforce.
|
||||
doc-checks:
|
||||
name: Run Documentation Checks (Doctests)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Examples that need a physical robot, a serial port or a Hub download are skipped by content.
|
||||
# Everything else has to actually run. See src/lerobot/utils/doctest_utils.py.
|
||||
SKIP_HARDWARE_DOCTEST: "1"
|
||||
SKIP_CUDA_DOCTEST: "1"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup uv and Python
|
||||
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
||||
with:
|
||||
enable-cache: true
|
||||
version: "0.11.30"
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install dependencies
|
||||
run: uv sync --locked --extra test --extra dataset
|
||||
|
||||
- name: Check the doctest list is sorted and its paths exist
|
||||
run: make check-doctest-list
|
||||
|
||||
- name: Check documented arguments match their signatures
|
||||
run: make check-docstrings
|
||||
|
||||
- name: Check docstring coverage has not regressed
|
||||
run: uv run --with interrogate interrogate --config=pyproject.toml
|
||||
|
||||
- name: Run doctests
|
||||
run: make doctest
|
||||
|
||||
+2
-11
@@ -67,11 +67,7 @@ repos:
|
||||
args: [--prose-wrap=preserve]
|
||||
# Jinja2 model-card templates use a .md extension but contain {% ... %} /
|
||||
# {{ ... }} tags that prettier's Markdown formatter mangles (e.g. table loops).
|
||||
#
|
||||
# docs/source/api/ holds the generated API reference. Its `[[autodoc]]` blocks restrict output
|
||||
# to an indented `- member` list, which prettier reads as a lazy paragraph continuation and
|
||||
# joins onto one line — silently turning a member list into part of the directive.
|
||||
exclude: ^(src/lerobot/templates/.*\.md|docs/source/api/.*\.mdx)$
|
||||
exclude: ^src/lerobot/templates/.*\.md$
|
||||
|
||||
##### Security #####
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
@@ -108,13 +104,8 @@ repos:
|
||||
# args: ["--docstring-style", "google", "-v", "2"]
|
||||
# exclude: ^tests/.*$
|
||||
|
||||
# interrogate runs in CI (quality.yml, doc-checks job) rather than here. Its 1.7.0 release still imports
|
||||
# the deprecated `py` package, which resolves against whatever `py` happens to be importable in
|
||||
# pre-commit's isolated env — on a machine with miniconda on the path that is a stray `py.py` and the
|
||||
# hook dies before it reads any config. The gate is the same either way; the CI step is just reliable.
|
||||
# - repo: https://github.com/econchick/interrogate
|
||||
# rev: 1.7.0
|
||||
# hooks:
|
||||
# - id: interrogate
|
||||
# args: ["--config=pyproject.toml"]
|
||||
# pass_filenames: false
|
||||
# args: ["-vv", "--config=pyproject.toml"]
|
||||
|
||||
@@ -50,10 +50,6 @@ To run checks manually on all files:
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### Docstrings
|
||||
|
||||
The API reference is generated from the docstrings in `src/lerobot/`. If you add or change anything public, follow the [docstring standard](https://huggingface.co/docs/lerobot/writing_docstrings) — the format is parsed by the renderer and checked in CI.
|
||||
|
||||
### Running Tests
|
||||
|
||||
We use `pytest`. First, ensure you have test artifacts by installing **git-lfs**:
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
# Interactive Rollout — Design Notes
|
||||
|
||||
Branch: `feat/add_interactive_rollout` · Status: Phases 1–2 committed; Round 2
|
||||
(programmatic API, sentry support, muting v2, stdin move) implemented and tested,
|
||||
uncommitted.
|
||||
|
||||
---
|
||||
|
||||
## 1. Vision
|
||||
|
||||
`lerobot-rollout` runs inference on a real robot: it connects hardware, loads the policy,
|
||||
builds the processor pipelines, optionally records a dataset, and spins the control loop.
|
||||
Today that is a **one-shot, fire-and-forget** program. You pass `--task="pick up the cube"`
|
||||
on the command line, the robot starts moving immediately, and the only interaction left is
|
||||
Ctrl-C. If you want a different instruction, you kill the process and pay the full startup
|
||||
cost again — reconnecting motors, re-homing, re-loading a multi-GB VLA onto the GPU.
|
||||
|
||||
Since LeRobot gained subtask annotation and language conditioning, that model is the
|
||||
bottleneck. The **north star** is a chat-style CLI over stdin, where the operator issues
|
||||
commands *concurrently with the robot moving*:
|
||||
|
||||
```
|
||||
/start begin (or resume) the policy control loop
|
||||
/subtask Grab the red cube re-instruct the policy on the fly
|
||||
/ask what's the capital of France? query an LLM while the robot keeps moving
|
||||
/reset stop movement, return home, clear the subtask —
|
||||
but keep hardware and policy warm
|
||||
/stop graceful shutdown
|
||||
```
|
||||
|
||||
The unifying idea: **the expensive things (hardware, policy weights, processors) stay warm
|
||||
across commands.** Only the cheap things — the instruction, the control loop — start and
|
||||
stop. That turns a rollout from a batch job into a session you can steer.
|
||||
|
||||
## 2. Objective (scoped)
|
||||
|
||||
Phased, so each phase lands as a reviewable unit:
|
||||
|
||||
| Phase | Scope | Status |
|
||||
|---|---|---|
|
||||
| **1** | `--interactive` flag, non-blocking stdin listener, command parser, `/start` `/reset` `/stop` `/help` | ✅ done |
|
||||
| **1.5** | Mute system logs so they stop fighting the prompt for the terminal | ✅ done |
|
||||
| **2** | `/subtask <text>` — change the policy's instruction mid-run | ✅ done |
|
||||
| **2.5** | Round 2: `RolloutController` public API, sentry recording support, muting v2 (errors surface), stdin listener → `lerobot/utils` | ✅ done (see §5) |
|
||||
| **3** | `/ask` + hierarchical task-vs-subtask semantics (LLM in the loop) | not started |
|
||||
|
||||
An explicit constraint through Phases 1–2: **do not couple this to the language runtime yet.**
|
||||
Build the mechanism; keep the door open.
|
||||
|
||||
## 3. Inspiration — three reference PRs
|
||||
|
||||
We read all three and deliberately implemented none of them verbatim.
|
||||
|
||||
**PR #4108 — online subtask switching.** Introduces a `PromptBroker` + `PromptListenerBase`
|
||||
+ `StdinPromptListener`, a `RuntimeContext.prompt_broker` field, `register_on_change`
|
||||
callbacks, an `--online_task_switching_flush` config flag, and `flush_action_queue()` /
|
||||
`_apply_pending_flush()` on `PreTrainedPolicy` — **with edits to 14 policy files** to call
|
||||
the flush at the top of `select_action`. Its architecture is designed for pluggable input
|
||||
sources (network, voice), which is the right long-term shape but more machinery than we
|
||||
need. *What we took:* the core insight that a mid-run instruction change must invalidate
|
||||
actions precomputed under the old instruction, and that the flush must happen on a thread
|
||||
that is safe to touch policy state from.
|
||||
|
||||
**PR #4183 — experimental full-UX draft.** Achieves the whole north-star vision, but does
|
||||
so by adding a `lerobot.runtime` / `language_runtime.py` that **duplicates** `BaseStrategy`,
|
||||
`send_next_action`, and the rollout control loop. *What we took:* the UX target and the
|
||||
command vocabulary. *What we rejected:* the parallel runtime — a second control loop is a
|
||||
second thing to keep correct, and everything it does is already in `rollout/strategies/`.
|
||||
|
||||
**PR #4234 — policy-side edits enabling #4183's runtime.** Read for context on where the
|
||||
language plumbing lands inside a policy. Relevant to Phase 3, not to what we built.
|
||||
|
||||
## 4. What we built, and why
|
||||
|
||||
Three commits on the branch:
|
||||
|
||||
```
|
||||
072c697c0 feat(rollout): interactive v1
|
||||
d3ee0b820 feat(rollout): mute logs in interactive mode
|
||||
39c4e746f feat(rollout): add subtask command
|
||||
```
|
||||
|
||||
Cumulative footprint — one new module, one new test file, small surgical edits elsewhere:
|
||||
|
||||
```
|
||||
src/lerobot/rollout/interactive.py | 580 +++++ (new)
|
||||
tests/test_interactive_rollout.py | 788 +++++ (new)
|
||||
docs/source/inference.mdx | 87 +++
|
||||
src/lerobot/rollout/inference/base.py | 66 +++
|
||||
src/lerobot/rollout/inference/rtc.py | 61 +-
|
||||
src/lerobot/rollout/inference/sync.py | 21 +-
|
||||
src/lerobot/scripts/lerobot_rollout.py | 32 +-
|
||||
src/lerobot/policies/pretrained.py | 24 +
|
||||
src/lerobot/rollout/strategies/core.py | 21 +-
|
||||
src/lerobot/rollout/configs.py | 18 +
|
||||
src/lerobot/rollout/__init__.py | 16 +-
|
||||
src/lerobot/rollout/strategies/episodic.py | 4 +-
|
||||
```
|
||||
|
||||
The ratio matters: **~1400 of ~1680 added lines are the new module and its tests.** The
|
||||
existing rollout architecture was reused, not reshaped.
|
||||
|
||||
### 4.1 Segments over a linked event — the load-bearing idea
|
||||
|
||||
Every rollout strategy's control loop already polls `ctx.runtime.shutdown_event.is_set()`
|
||||
to know when to stop. So instead of teaching strategies about interactivity, we **swap in a
|
||||
smarter event**:
|
||||
|
||||
```python
|
||||
class LinkedEvent(Event):
|
||||
"""is_set() reflects the local flag OR a parent event."""
|
||||
def is_set(self) -> bool:
|
||||
return super().is_set() or self.parent.is_set()
|
||||
```
|
||||
|
||||
`lerobot-rollout` wraps the `ProcessSignalHandler`'s shutdown event in a `LinkedEvent` when
|
||||
`--interactive=true`. The session sets the **local** flag to end a run *segment*; SIGINT /
|
||||
SIGTERM still arrive through the **parent**, so Ctrl-C behaves exactly as before.
|
||||
|
||||
`InteractiveSession.run()` then drives `strategy.run(ctx)` in restartable segments:
|
||||
|
||||
```
|
||||
setup(ctx) → [idle] → /start → run(ctx) → /reset → [idle] → /start → run(ctx) → /stop → teardown(ctx)
|
||||
↑ hardware + policy stay warm throughout
|
||||
```
|
||||
|
||||
**Zero strategy code changed** to support this. The only additions to `strategies/core.py`
|
||||
were `reset_control_state()` (engine + interpolator + cached-observation reset, factored
|
||||
out of `_init_engine` so a segment can restart cleanly) and making
|
||||
`_return_to_initial_position` public.
|
||||
|
||||
### 4.2 Threading model
|
||||
|
||||
```
|
||||
listener thread ──publishes flags / strings──▶ main thread
|
||||
(stdin reader) never touches hardware (session loop → strategy.run → control loop)
|
||||
never mutates policy state
|
||||
```
|
||||
|
||||
The listener only ever writes `threading.Event` flags and a lock-guarded string. Everything
|
||||
that touches hardware or policy state happens on the thread that already owns it. This
|
||||
mirrors the existing DAgger events pattern rather than inventing a new concurrency idiom.
|
||||
|
||||
### 4.3 stdin must be read with `os.read`, not `readline`
|
||||
|
||||
Non-obvious and load-bearing. The first implementation used `select()` + `stream.readline()`
|
||||
and **two tests failed**: a buffered file object slurps *several* lines off the file
|
||||
descriptor in one syscall, after which `select` reports the drained fd as not-ready and the
|
||||
buffered lines are never delivered. Pasted or piped command batches got stuck. The reader
|
||||
now does `select()` + `os.read(fd, 4096)` + manual `\n` splitting, with a
|
||||
blocking-`readline` fallback for streams without a `fileno()` (non-POSIX, test doubles).
|
||||
|
||||
Also: unlike `TerminalKeyListener`, this reader leaves the terminal in **canonical mode** —
|
||||
the operator is typing chat commands, not pressing hotkeys.
|
||||
|
||||
### 4.4 EOF means stop
|
||||
|
||||
A closed stdin means there is no way left to command the robot, so EOF (Ctrl-D, or an
|
||||
exhausted piped script) stops the session. An unexpected read error is treated the same way,
|
||||
for the same reason. Consequence, documented: piped scripts must hold stdin open —
|
||||
|
||||
```bash
|
||||
(printf '/start\n'; sleep 60; printf '/stop\n') | lerobot-rollout ... --interactive=true
|
||||
```
|
||||
|
||||
### 4.5 Commands are last-write-wins
|
||||
|
||||
`/reset` and `/stop` cancel a still-pending `/start`, so the robot never starts moving after
|
||||
the operator's most recent command said not to. Handlers set their intent flag *first* and
|
||||
the segment-stop event *second*; `_run_segment` clears the segment-stop flag *before*
|
||||
re-checking the intent flags. A `/reset` racing a `/start` is therefore either seen before
|
||||
the segment begins or ends it on its first tick.
|
||||
|
||||
### 4.6 Base strategy only (enforced by config validation)
|
||||
|
||||
`--interactive=true` with a recording strategy raises a `ValueError`. Two reasons: recording
|
||||
strategies finalize their dataset inside `run()` (so `run()` is not restartable), and their
|
||||
keyboard listeners contend with the command reader for the same TTY. This is a deliberate,
|
||||
documented limitation — not an oversight.
|
||||
|
||||
### 4.7 Log muting (Phase 1.5)
|
||||
|
||||
Policy, robot and control-loop logs at every level interleave with the chat prompt and
|
||||
destroy the typing UX. Simplest workable answer, per explicit request: **mute console output
|
||||
for the duration of the session.**
|
||||
|
||||
- Every logger's console `StreamHandler` is raised above `CRITICAL` — **not just root**,
|
||||
because `transformers` and `datasets` attach their own stderr handlers with
|
||||
`propagate=False`.
|
||||
- `warnings.simplefilter("ignore")`, with `warnings.filters` saved and restored.
|
||||
- **File handlers are untouched** — anyone wanting a persistent log can attach one.
|
||||
- Restored in `run()`'s `finally`, *before* the closing `log_say`, so teardown logs are visible.
|
||||
|
||||
The obvious hazard: muting hides fatal errors. So `InferenceEngine` gained a
|
||||
`failure_traceback` property, RTC captures its traceback in the fatal handler, and the
|
||||
session prints it on failure. **Do not remove that when touching the failure path.**
|
||||
|
||||
"See both logs and prompt" — a pinned input line, `prompt_toolkit`-style — was deliberately
|
||||
deferred: it needs a new dependency and a real TUI layer.
|
||||
|
||||
### 4.8 `/subtask` — the engine *is* the broker
|
||||
|
||||
The pivotal call on Phase 2: **skip PR #4108's `PromptBroker`.** After Phase 1, the session
|
||||
already owns the stdin thread and the parser, so a broker + listener base + on-change
|
||||
callbacks + a new `RuntimeContext` field would be duplicate machinery — and callbacks firing
|
||||
on the listener thread are exactly the cross-thread hazard we designed against.
|
||||
|
||||
Instead, `InferenceEngine` (the ABC every backend already implements) became the thread-safe
|
||||
task holder:
|
||||
|
||||
```python
|
||||
@property
|
||||
def task(self) -> str: ... # lock-guarded read
|
||||
|
||||
def set_task(self, task) -> bool: # callable from ANY thread; True if it changed
|
||||
...
|
||||
|
||||
def _take_task(self) -> tuple[str, bool]: # consumed on the INFERENCE thread;
|
||||
... # returns (task, changed) and clears the edge
|
||||
```
|
||||
|
||||
`/subtask` is then three lines: read `engine.task`, call `engine.set_task(text)`, print the
|
||||
transition. No new module, no new context field, no callbacks.
|
||||
|
||||
**The flush problem, and why it got small.** When the instruction changes, a chunking policy
|
||||
is still serving actions computed under the old one — up to `chunk_size` ticks of stale
|
||||
behavior. PR #4108 solved this by adding `flush_action_queue()` / `_apply_pending_flush()`
|
||||
to `PreTrainedPolicy` **and editing 14 policy files**, because its flush request arrived from
|
||||
a foreign thread and had to be deferred to a safe point inside `select_action`.
|
||||
|
||||
Ours already runs *on* the thread that calls `select_action`. So: one concrete method on
|
||||
`PreTrainedPolicy` and **zero per-policy edits**.
|
||||
|
||||
```python
|
||||
def drop_queued_actions(self) -> None:
|
||||
queues = getattr(self, "_queues", None)
|
||||
if isinstance(queues, dict) and ACTION in queues:
|
||||
queues[ACTION].clear()
|
||||
action_queue = getattr(self, "_action_queue", None)
|
||||
if action_queue is not None:
|
||||
action_queue.clear()
|
||||
```
|
||||
|
||||
Two `getattr`s cover the repo's two queue idioms across all ~18 policies
|
||||
(`_queues[ACTION]`: diffusion, smolvla, tdmpc, vqbet, wall_x, xvla, multi_task_dit, vla_jepa;
|
||||
`_action_queue`: act, pi0, pi05, pi0_fast, eo1, evo1, groot, molmoact2, fastwam, lingbot_va).
|
||||
Policies with no queue inherit a no-op.
|
||||
|
||||
**Why not `policy.reset()`?** That was the first implementation, and review caught it as too
|
||||
blunt. For Diffusion it wipes the observation history, so the next chunk is planned from a
|
||||
history of the current frame repeated — a visible discontinuity mid-motion. And ACT /
|
||||
Diffusion / VQBeT / TDMPC don't read `task` at all, so they'd pay that jerk for nothing.
|
||||
`drop_queued_actions` keeps episode state and drops only what is actually stale.
|
||||
|
||||
**RTC deliberately does *not* flush.** Clearing its queue would leave the robot with no
|
||||
commands for a full inference latency (~1 s on a VLA). Instead the next chunk is generated
|
||||
under the new instruction and merged over the previous chunk's leftover prefix — the switch
|
||||
lands within one inference and the motion stays continuous. That is exactly what RTC's
|
||||
blending exists for. Documented per-backend in `inference.mdx`; no config flag, one sensible
|
||||
default per backend.
|
||||
|
||||
**`/reset` restores the launch task on the listener thread.** Subtle and worth preserving:
|
||||
the restore lives in `_cmd_reset`, not in `_reset_robot` (which runs later, on the main
|
||||
thread). Otherwise `/reset` followed immediately by `/subtask` would be ordered by *service*
|
||||
time rather than *command* time, and the deferred restore would silently revert the new
|
||||
instruction — deterministically so, for pasted or piped input. Both writers now run on the
|
||||
same thread, so command order wins. There is a regression test driving this through a real pipe.
|
||||
|
||||
## 5. Round 2 — the feature becomes a library API
|
||||
|
||||
Four follow-up asks landed together (currently uncommitted on the branch):
|
||||
make the components programmatic-API friendly (the priority), extend interactive
|
||||
to recording where cheap, simplify muting / surface errors, and settle the
|
||||
ssh/headless + `keyboard_input` question.
|
||||
|
||||
### 5.1 `RolloutController` — programmatic control
|
||||
|
||||
`interactive.py` bisected cleanly, so the generic control logic moved to a new
|
||||
`rollout/controller.py`:
|
||||
|
||||
```python
|
||||
controller = RolloutController(strategy, ctx, on_event=my_observer)
|
||||
controller.serve() # blocking loop (run it on whatever thread you like)
|
||||
controller.start() # -> bool: False when a segment is already running
|
||||
controller.set_task(t) # -> bool: re-instruct mid-run, from any thread
|
||||
controller.reset() # -> bool: True when the launch task was restored
|
||||
controller.stop()
|
||||
controller.task / .initial_task / .running / .failed / .failure_traceback
|
||||
```
|
||||
|
||||
- **No I/O of its own** — no stdin, no prints, no log muting, no TTS. Every
|
||||
state transition that used to be a `print` is now a `RolloutEvent`
|
||||
(`SEGMENT_STARTED`, `SEGMENT_ENDED`, `RESET_STARTED/DONE/SKIPPED`,
|
||||
`ENGINE_FAILED`, `STOPPED`) emitted on the serve thread.
|
||||
- **Thread-safe by lock, not by convention.** The old ordering guarantee
|
||||
(`/subtask` right after `/reset` must win) relied on both writes running on
|
||||
the single stdin thread. The controller serializes `start`/`reset`/`stop`/
|
||||
`set_task` with an internal lock, so the guarantee now holds for arbitrary
|
||||
caller threads — the prerequisite for network/voice front-ends.
|
||||
- `InteractiveSession` shrank to a thin adapter: stdin listener + parser +
|
||||
rendering + muting; each command maps 1:1 onto a controller method, and the
|
||||
controller is exposed as `session.controller`.
|
||||
- Exported from `lerobot.rollout`: `RolloutController`, `RolloutEvent`,
|
||||
`LinkedEvent`. `docs/source/inference.mdx` gained a **Programmatic control**
|
||||
section with a complete embedding example.
|
||||
|
||||
### 5.2 Sentry + interactive — recording while you steer
|
||||
|
||||
Decision, per the agreed criteria: the `/record` keyboard-handoff idea is
|
||||
**medium-to-large** (listeners have no suspend/resume API and start at
|
||||
creation, `esc` handlers are hardcoded and collide, pynput captures globally
|
||||
while you type, and each strategy carries per-run stale flags) → rejected.
|
||||
But the investigation showed **sentry has zero keyboard code** — the config
|
||||
comment lumping it with the keyboard strategies was simply wrong — and its
|
||||
only real blocker was one line: `with VideoEncodingManager(dataset)` inside
|
||||
`run()` finalizes the dataset the first time `run()` returns, after which a
|
||||
restarted segment would silently truncate the finalized parquet.
|
||||
|
||||
So `--interactive=true` now supports `--strategy.type=sentry`:
|
||||
|
||||
- **Finalization moved to `teardown()`** (which already called
|
||||
`dataset.finalize()`); `run()` is segment-restartable. Each segment saves
|
||||
complete episodes plus one tail partial episode; on a failed tail save the
|
||||
in-flight streaming encode is cancelled *and* the half-mutated episode
|
||||
buffer is discarded (see §6, round 2).
|
||||
- **Frames are labeled with the live `engine.task`** instead of a config
|
||||
snapshot — the writer already stores a task per frame — so `/subtask`
|
||||
changes the policy conditioning and the recorded label from the same frame
|
||||
onward. This also resolved the "recorded frames ignore `/subtask`" open item
|
||||
for sentry.
|
||||
- `episodes_since_push` hoisted to instance state so upload cadence survives
|
||||
segments.
|
||||
- dagger / highlight / episodic stay excluded: keyboard conflicts plus per-run
|
||||
recording state that does not survive a restart.
|
||||
|
||||
### 5.3 Muting v2 — two lines, and errors surface
|
||||
|
||||
The ~30-line per-handler walk became `logging.disable(logging.WARNING)` with
|
||||
the previous disable level restored afterwards. Strictly better coverage: the
|
||||
gate applies before handler dispatch, so it covers `propagate=False` library
|
||||
loggers *and* loggers created mid-session (the old snapshot missed those) —
|
||||
and **ERROR/CRITICAL now reach the console**, which the audit showed is safe:
|
||||
no ERROR-level emitter fires periodically in healthy operation (the periodic
|
||||
nuisances — slow-loop, camera hiccups — are WARNINGs and stay muted).
|
||||
Documented trade-off: the gate also withholds INFO/WARNING from file handlers
|
||||
during the session; acceptable because no default code path attaches one
|
||||
(only `rl/actor`, `rl/learner`, `async_inference` pass `log_file`). The
|
||||
`warnings` suppression stays (nothing calls `logging.captureWarnings`), and
|
||||
`failure_traceback` surfacing stays as the belt-and-suspenders for fatal
|
||||
engine errors.
|
||||
|
||||
### 5.4 stdin listener → `lerobot/utils/stdin_input.py`
|
||||
|
||||
The ssh/headless audit confirmed the listener was already the right design:
|
||||
`select`+`os.read` works over SSH (the session pty is a normal fd), from
|
||||
pipes, and headless — it's `keyboard_input`'s **pynput** backend that needs a
|
||||
display server. Nothing in `keyboard_input` overlaps enough to reuse
|
||||
(1-byte cbreak hotkey decoding vs canonical-mode line assembly), so
|
||||
`StdinCommandListener` moved to a **new** utils module — deliberately not
|
||||
into `keyboard_input.py`, which attempts a pynput import at module load.
|
||||
Canonical import only: `lerobot.utils.stdin_input` (removed from
|
||||
`lerobot.rollout`'s exports).
|
||||
|
||||
The move fixed a real bug the audit found: with `sys.stdin is None`
|
||||
(daemonized processes), the blocking fallback died with an uncaught
|
||||
`AttributeError` without firing `on_eof` — leaving a session idling with no
|
||||
command channel. `start()` now treats a missing stream as immediate EOF.
|
||||
|
||||
## 6. Bugs the adversarial reviews caught
|
||||
|
||||
Four multi-agent review passes were run across the phases (28 / 5 / 27 / 12 agents;
|
||||
findings adversarially verified before acting). The ones that mattered:
|
||||
|
||||
**Round 2 (2 confirmed, 0 refuted):**
|
||||
|
||||
- **Controller `start()` race → phantom segment.** `start()` gated on `_running`, but the
|
||||
serve loop cleared `_start_requested` *before* setting `_running` — a second `start()`
|
||||
landing in that window (spanning `reset_control_state` and the SEGMENT_STARTED emission)
|
||||
returned `True` and re-armed the flag, which nothing consumed during the segment; the
|
||||
robot would start again, uncommanded, when the segment later ended on its own. Fixed:
|
||||
the serve loop consumes the request and sets `_running` atomically under the control
|
||||
lock, and `_running` spans the whole startup sequence.
|
||||
- **Sentry poisoned episode buffer.** `save_episode` mutates the buffer in place (pops
|
||||
`size`/`task`) *before* the fallible writes; a failed tail save left a half-mutated dict
|
||||
and the next segment's first `add_frame` crashed with `KeyError('size')`. Fixed: the
|
||||
except branch discards the buffer so `add_frame` recreates it.
|
||||
|
||||
**Rounds 1–3 (Phases 1–2):**
|
||||
|
||||
- **RTC stale observation (critical).** `RTCInferenceEngine.reset()` never cleared
|
||||
`_obs_holder["obs"]`. After `/reset` physically moved the robot home, the next `/start`
|
||||
computed its first chunk from the **pre-reset pose** — a lurch back toward where the arm
|
||||
used to be. Fixed by clearing the observation and adding a `_reset_epoch` counter so an
|
||||
in-flight chunk computed across a reset is discarded rather than merged. This also fixes a
|
||||
pre-existing DAgger staleness path.
|
||||
- **Muting hid fatal errors** → `failure_traceback` capture + session print (§4.7).
|
||||
- **Muting scope too narrow** → root-only missed `transformers` / `datasets`; `warnings`
|
||||
output bypassed logging entirely.
|
||||
- **Command ordering** → `/reset` and `/stop` didn't cancel a pending `/start` (§4.5); the
|
||||
`/reset`-then-`/subtask` clobber (§4.8).
|
||||
- **Flush too heavy** → `policy.reset()` → `drop_queued_actions()` (§4.8).
|
||||
- **Empty-task rendering** → `''` replaced with `(none — set one with /subtask <text>)`.
|
||||
- **Silent switch** → the confirmation now says "(applies from the next policy inference)",
|
||||
since the explanatory logs are muted.
|
||||
|
||||
## 7. Verification
|
||||
|
||||
After Round 2:
|
||||
|
||||
```
|
||||
uv run --extra dataset pytest tests/test_interactive_rollout.py \
|
||||
tests/utils/test_stdin_input.py tests/test_rollout.py -q
|
||||
→ 81 passed
|
||||
|
||||
pre-commit (all changed files)
|
||||
→ 0 failures
|
||||
```
|
||||
|
||||
Phase 1–2 numbers (still green at the time): 64 rollout/interactive tests;
|
||||
223 passed / 5 skipped across `tests/policies/rtc`, factory, and common
|
||||
(confirming the shared `pretrained.py` change); pre-commit 0 failures.
|
||||
|
||||
`tests/test_interactive_rollout.py` covers the parser, `LinkedEvent` semantics,
|
||||
`RolloutController` (start/reset/stop/set_task flows, events, startup-race
|
||||
rejection, failure surfacing, broken observers), session flows (start / reset /
|
||||
restart / stop, cancel-pending-start, engine failure with traceback, natural
|
||||
end, EOF, and a real `BaseStrategy` end-to-end), muting (INFO/WARNING blocked,
|
||||
ERROR surfaces, pre-existing disable level restored), `/subtask` semantics,
|
||||
sentry restartability + live labels + failed-tail-save recovery, the engine
|
||||
task holder, the sync flush, and `drop_queued_actions`.
|
||||
`tests/utils/test_stdin_input.py` covers the listener (select path, batched
|
||||
lines, blocking fallback, EOF, handler errors, None-stdin, broken streams).
|
||||
|
||||
## 8. Extension points for Phase 3
|
||||
|
||||
The design was built to make `/ask` an additive change:
|
||||
|
||||
- **Command table.** `InteractiveSession._commands` is `name → (handler, arg hint, help)`.
|
||||
`/help` and the startup banner render from it, so a new command is documented for free.
|
||||
- **Controller API.** New front-ends (network, voice, `/ask`'s LLM worker) call
|
||||
`RolloutController.start/reset/stop/set_task` from their own threads — the internal lock
|
||||
makes that safe — and observe `RolloutEvent`s instead of scraping terminal output.
|
||||
- **Thread discipline.** A command handler runs on the listener thread and must only call
|
||||
controller methods. An LLM call belongs on its own worker thread so the robot keeps
|
||||
moving — precisely the concurrency `/ask` is meant to demonstrate.
|
||||
- **Task holder.** `set_task` / `_take_task` already give any producer a safe way to
|
||||
re-instruct the policy. Hierarchical task-vs-subtask semantics (per #4183 / #4234) layer
|
||||
on top of it rather than replacing it.
|
||||
|
||||
Open items, deliberately not addressed:
|
||||
|
||||
- dagger / highlight / episodic remain non-interactive (keyboard conflicts + per-run
|
||||
recording state); they also still snapshot the task label per run. Sentry is the
|
||||
supported recording path for interactive sessions.
|
||||
- The "see logs and prompt simultaneously" TUI (pinned input line).
|
||||
- Non-stdin input sources (network, voice) — now unblocked by `RolloutController`; #4108's
|
||||
pluggable-listener shape remains the reference for the transport layer.
|
||||
@@ -184,29 +184,3 @@ test-smolvla-ete-eval:
|
||||
# backend, so it does not require a real model checkpoint or GPU.
|
||||
annotation-e2e:
|
||||
uv run python -m tests.annotations.run_e2e_smoke
|
||||
|
||||
# Docstring & doctest checks. See docs/source/writing_docstrings.mdx for the standard these enforce.
|
||||
|
||||
# Run the examples in the docstrings listed in utils/documentation_tests.txt. Hardware and GPU examples are
|
||||
# skipped by content (see src/lerobot/utils/doctest_utils.py); CI sets both flags.
|
||||
doctest:
|
||||
@files=$$(grep -v '^\s*#' utils/documentation_tests.txt | grep -v '^\s*$$'); \
|
||||
if [ -z "$$files" ]; then \
|
||||
echo "utils/documentation_tests.txt lists no files; nothing to run."; \
|
||||
else \
|
||||
SKIP_HARDWARE_DOCTEST=1 uv run pytest --doctest-modules --no-header -q $$files; \
|
||||
fi
|
||||
|
||||
check-doctest-list:
|
||||
uv run python utils/check_doctest_list.py
|
||||
|
||||
fix-doctest-list:
|
||||
uv run python utils/check_doctest_list.py --fix_and_overwrite
|
||||
|
||||
check-docstrings:
|
||||
uv run python utils/check_docstrings.py
|
||||
uv run python utils/check_config_docstrings.py
|
||||
|
||||
fix-docstrings:
|
||||
uv run python utils/check_docstrings.py --fix_and_overwrite
|
||||
uv run python utils/check_doctest_list.py --fix_and_overwrite
|
||||
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
# Copyright 2026 The HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Root conftest: makes doctest collection use LeRobot's parser.
|
||||
|
||||
This only affects `--doctest-modules` runs (see `make doctest`). The test suite itself is configured by
|
||||
`tests/conftest.py`.
|
||||
"""
|
||||
|
||||
import doctest
|
||||
|
||||
import _pytest.doctest
|
||||
|
||||
from lerobot.utils.doctest_utils import LeRobotDoctestModule, LeRobotDocTestParser
|
||||
|
||||
# Lets an example opt out of output comparison with `# doctest: +IGNORE_RESULT`, for calls whose output is
|
||||
# a progress bar or otherwise not reproducible.
|
||||
IGNORE_RESULT = doctest.register_optionflag("IGNORE_RESULT")
|
||||
|
||||
OutputChecker = doctest.OutputChecker
|
||||
|
||||
|
||||
class CustomOutputChecker(OutputChecker):
|
||||
"""An output checker that honours the `IGNORE_RESULT` flag."""
|
||||
|
||||
def check_output(self, want, got, optionflags):
|
||||
"""Return `True` when `IGNORE_RESULT` is set, otherwise defer to stdlib.
|
||||
|
||||
Args:
|
||||
want (`str`):
|
||||
The expected output.
|
||||
got (`str`):
|
||||
The actual output.
|
||||
optionflags (`int`):
|
||||
Bitmask of active doctest option flags.
|
||||
|
||||
Returns:
|
||||
`bool`: Whether the output is considered a match.
|
||||
"""
|
||||
if IGNORE_RESULT & optionflags:
|
||||
return True
|
||||
return OutputChecker.check_output(self, want, got, optionflags)
|
||||
|
||||
|
||||
# Reassigning these module attributes is how doctest behaviour is customised; mypy sees it as assigning to
|
||||
# a type, which is exactly what is intended here.
|
||||
doctest.OutputChecker = CustomOutputChecker # type: ignore[misc]
|
||||
_pytest.doctest.DoctestModule = LeRobotDoctestModule
|
||||
doctest.DocTestParser = LeRobotDocTestParser # type: ignore[misc]
|
||||
@@ -191,28 +191,6 @@
|
||||
- sections:
|
||||
- local: contributing
|
||||
title: Contribute to LeRobot
|
||||
- local: writing_docstrings
|
||||
title: Writing docstrings
|
||||
- local: backwardcomp
|
||||
title: Backward compatibility
|
||||
title: "About"
|
||||
- sections:
|
||||
- local: api/robots
|
||||
title: Robots
|
||||
- local: api/teleoperators
|
||||
title: Teleoperators
|
||||
- local: api/cameras
|
||||
title: Cameras
|
||||
- local: api/motors
|
||||
title: Motors
|
||||
- local: api/datasets
|
||||
title: Datasets
|
||||
- local: api/policies
|
||||
title: Policies
|
||||
- local: api/processor
|
||||
title: Processors
|
||||
- local: api/envs
|
||||
title: Environments
|
||||
- local: api/configs
|
||||
title: Configuration
|
||||
title: "API Reference"
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Cameras
|
||||
|
||||
Cameras supply the image observations a policy sees. Every backend — OpenCV, Intel RealSense, Reachy 2 —
|
||||
implements the [`Camera`] interface, so swapping hardware does not change the code that reads frames.
|
||||
|
||||
See the [Cameras guide](../cameras) for choosing and configuring a camera, and
|
||||
[Third-Party Cameras & Sensors](../third_party_sensors) for devices outside the core set.
|
||||
|
||||
## Camera
|
||||
|
||||
[[autodoc]] lerobot.cameras.Camera
|
||||
- connect
|
||||
- disconnect
|
||||
- read
|
||||
- async_read
|
||||
- find_cameras
|
||||
|
||||
## CameraConfig
|
||||
|
||||
[[autodoc]] lerobot.cameras.CameraConfig
|
||||
|
||||
## make_cameras_from_configs
|
||||
|
||||
[[autodoc]] lerobot.cameras.make_cameras_from_configs
|
||||
@@ -1,27 +0,0 @@
|
||||
# Configuration
|
||||
|
||||
LeRobot configuration is plain dataclasses parsed by [draccus](https://github.com/dlwh/draccus), so every
|
||||
field is settable from the CLI. [`TrainPipelineConfig`] is the top-level object for `lerobot-train`.
|
||||
|
||||
Polymorphic configs (policies, robots, environments) use `draccus.ChoiceRegistry`: a subclass registers
|
||||
itself with `@register_subclass("name")` and is then selectable by that name on the command line.
|
||||
|
||||
## TrainPipelineConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.train.TrainPipelineConfig
|
||||
|
||||
## PreTrainedConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.PreTrainedConfig
|
||||
|
||||
## DatasetConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.DatasetConfig
|
||||
|
||||
## EvalConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.EvalConfig
|
||||
|
||||
## WandBConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.WandBConfig
|
||||
@@ -1,23 +0,0 @@
|
||||
# Datasets
|
||||
|
||||
[`LeRobotDataset`] is the format every LeRobot script reads and writes. It is episode-aware, decodes video
|
||||
observations on the fly, and round-trips to the Hugging Face Hub.
|
||||
|
||||
See [Using LeRobotDataset](../lerobot-dataset-v3) for the format and the common operations,
|
||||
[Porting Large Datasets](../porting_datasets_v3) for migration, and [Tools](../tools) for the CLI.
|
||||
|
||||
## LeRobotDataset
|
||||
|
||||
[[autodoc]] lerobot.datasets.LeRobotDataset
|
||||
|
||||
## LeRobotDatasetMetadata
|
||||
|
||||
[[autodoc]] lerobot.datasets.LeRobotDatasetMetadata
|
||||
|
||||
## MultiLeRobotDataset
|
||||
|
||||
[[autodoc]] lerobot.datasets.MultiLeRobotDataset
|
||||
|
||||
## StreamingLeRobotDataset
|
||||
|
||||
[[autodoc]] lerobot.datasets.StreamingLeRobotDataset
|
||||
@@ -1,19 +0,0 @@
|
||||
# Environments
|
||||
|
||||
Simulation environments are configured through [`EnvConfig`] and built by [`make_env`]. Each subclass
|
||||
declares its `gym_kwargs` and how to construct the vectorised environments.
|
||||
|
||||
See [Environments from the Hub](../envhub) for using published environments and
|
||||
[Adding a New Benchmark](../adding_benchmarks) for contributing one.
|
||||
|
||||
## EnvConfig
|
||||
|
||||
[[autodoc]] lerobot.envs.EnvConfig
|
||||
|
||||
## make_env
|
||||
|
||||
[[autodoc]] lerobot.envs.make_env
|
||||
|
||||
## make_env_config
|
||||
|
||||
[[autodoc]] lerobot.envs.make_env_config
|
||||
@@ -1,23 +0,0 @@
|
||||
# Motors
|
||||
|
||||
`MotorsBus` is the low-level interface to a chain of servos on a serial bus. Robots use it to read positions
|
||||
and write goal positions; you rarely touch it directly unless you are adding hardware.
|
||||
|
||||
See [Bring Your Own Hardware](../integrate_hardware) for adding a new bus, and
|
||||
[Updating Feetech Firmware](../feetech) and [Damiao Motors and CAN Bus](../damiao) for device-specific notes.
|
||||
|
||||
## MotorsBus
|
||||
|
||||
[[autodoc]] lerobot.motors.motors_bus.MotorsBus
|
||||
|
||||
## Motor
|
||||
|
||||
[[autodoc]] lerobot.motors.Motor
|
||||
|
||||
## MotorCalibration
|
||||
|
||||
[[autodoc]] lerobot.motors.MotorCalibration
|
||||
|
||||
## MotorNormMode
|
||||
|
||||
[[autodoc]] lerobot.motors.MotorNormMode
|
||||
@@ -1,177 +0,0 @@
|
||||
# Policies
|
||||
|
||||
Every policy inherits [`PreTrainedPolicy`], which combines a `torch.nn.Module` with the Hub mixin, so any
|
||||
policy can be pushed to and loaded from the Hugging Face Hub with the same two calls.
|
||||
|
||||
Each policy has its own guide with training recipes and results — [ACT](../act), [SmolVLA](../smolvla),
|
||||
[π₀](../pi0), [π₀.₅](../pi05) and the rest are listed under Policies. To add one, see
|
||||
[Adding a Policy](../bring_your_own_policies).
|
||||
|
||||
## PreTrainedPolicy
|
||||
|
||||
The abstract base class every policy subclasses. `forward` computes the training loss, `select_action`
|
||||
returns one action at a time for control loops, and `predict_action_chunk` returns a full action chunk.
|
||||
|
||||
[[autodoc]] lerobot.policies.pretrained.PreTrainedPolicy
|
||||
- forward
|
||||
- predict_action_chunk
|
||||
- select_action
|
||||
- get_optim_params
|
||||
- reset
|
||||
- from_pretrained
|
||||
- supports_rtc
|
||||
- push_model_to_hub
|
||||
- wrap_with_peft
|
||||
|
||||
## PreTrainedConfig
|
||||
|
||||
[[autodoc]] lerobot.configs.PreTrainedConfig
|
||||
|
||||
## make_policy
|
||||
|
||||
[[autodoc]] lerobot.policies.factory.make_policy
|
||||
|
||||
## get_policy_class
|
||||
|
||||
[[autodoc]] lerobot.policies.factory.get_policy_class
|
||||
|
||||
## make_policy_config
|
||||
|
||||
[[autodoc]] lerobot.policies.factory.make_policy_config
|
||||
|
||||
## make_pre_post_processors
|
||||
|
||||
[[autodoc]] lerobot.policies.factory.make_pre_post_processors
|
||||
|
||||
## ACT
|
||||
|
||||
[[autodoc]] lerobot.policies.act.modeling_act.ACTPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.act.configuration_act.ACTConfig
|
||||
|
||||
## SmolVLA
|
||||
|
||||
[[autodoc]] lerobot.policies.smolvla.modeling_smolvla.SmolVLAPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.smolvla.configuration_smolvla.SmolVLAConfig
|
||||
|
||||
## π₀ (PI0)
|
||||
|
||||
[[autodoc]] lerobot.policies.pi0.modeling_pi0.PI0Policy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.pi0.configuration_pi0.PI0Config
|
||||
|
||||
## π₀-FAST (PI0Fast)
|
||||
|
||||
[[autodoc]] lerobot.policies.pi0_fast.modeling_pi0_fast.PI0FastPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.pi0_fast.configuration_pi0_fast.PI0FastConfig
|
||||
|
||||
## π₀.₅ (PI05)
|
||||
|
||||
[[autodoc]] lerobot.policies.pi05.modeling_pi05.PI05Policy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.pi05.configuration_pi05.PI05Config
|
||||
|
||||
## MolmoAct2
|
||||
|
||||
[[autodoc]] lerobot.policies.molmoact2.modeling_molmoact2.MolmoAct2Policy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.molmoact2.configuration_molmoact2.MolmoAct2Config
|
||||
|
||||
## VLA-JEPA
|
||||
|
||||
[[autodoc]] lerobot.policies.vla_jepa.modeling_vla_jepa.VLAJEPAPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.vla_jepa.configuration_vla_jepa.VLAJEPAConfig
|
||||
|
||||
## EO-1
|
||||
|
||||
[[autodoc]] lerobot.policies.eo1.modeling_eo1.EO1Policy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.eo1.configuration_eo1.EO1Config
|
||||
|
||||
## LingBot-VA
|
||||
|
||||
[[autodoc]] lerobot.policies.lingbot_va.modeling_lingbot_va.LingBotVAPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.lingbot_va.configuration_lingbot_va.LingBotVAConfig
|
||||
|
||||
## FastWAM
|
||||
|
||||
[[autodoc]] lerobot.policies.fastwam.modeling_fastwam.FastWAMPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.fastwam.configuration_fastwam.FastWAMConfig
|
||||
|
||||
## EVO1
|
||||
|
||||
[[autodoc]] lerobot.policies.evo1.modeling_evo1.Evo1Policy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.evo1.configuration_evo1.Evo1Config
|
||||
|
||||
## NVIDIA GR00T
|
||||
|
||||
[[autodoc]] lerobot.policies.groot.modeling_groot.GrootPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.groot.configuration_groot.GrootConfig
|
||||
|
||||
## X-VLA
|
||||
|
||||
[[autodoc]] lerobot.policies.xvla.modeling_xvla.XVLAPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.xvla.configuration_xvla.XVLAConfig
|
||||
|
||||
## Multitask DiT Policy
|
||||
|
||||
[[autodoc]] lerobot.policies.multi_task_dit.modeling_multi_task_dit.MultiTaskDiTPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.multi_task_dit.configuration_multi_task_dit.MultiTaskDiTConfig
|
||||
|
||||
## WALL-OSS
|
||||
|
||||
[[autodoc]] lerobot.policies.wall_x.modeling_wall_x.WallXPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.wall_x.configuration_wall_x.WallXConfig
|
||||
|
||||
## Diffusion Policy
|
||||
|
||||
[[autodoc]] lerobot.policies.diffusion.modeling_diffusion.DiffusionPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.diffusion.configuration_diffusion.DiffusionConfig
|
||||
|
||||
## Gaussian Actor
|
||||
|
||||
[[autodoc]] lerobot.policies.gaussian_actor.modeling_gaussian_actor.GaussianActorPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.gaussian_actor.configuration_gaussian_actor.GaussianActorConfig
|
||||
|
||||
## TD-MPC
|
||||
|
||||
[[autodoc]] lerobot.policies.tdmpc.modeling_tdmpc.TDMPCPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.tdmpc.configuration_tdmpc.TDMPCConfig
|
||||
|
||||
## VQ-BeT
|
||||
|
||||
[[autodoc]] lerobot.policies.vqbet.modeling_vqbet.VQBeTPolicy
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.policies.vqbet.configuration_vqbet.VQBeTConfig
|
||||
@@ -1,20 +0,0 @@
|
||||
# Processors
|
||||
|
||||
Processors are the data transformation layer between a robot, a dataset and a policy. A pipeline is a chain
|
||||
of [`ProcessorStep`]s; each step declares how it transforms both the data and the feature contract.
|
||||
|
||||
See [Introduction to Robot Processors](../introduction_processors) for the concepts,
|
||||
[Implement your own processor](../implement_your_own_processor) to write a step, and
|
||||
[Debug your processor pipeline](../debug_processor_pipeline) when a pipeline misbehaves.
|
||||
|
||||
## ProcessorStep
|
||||
|
||||
[[autodoc]] lerobot.processor.pipeline.ProcessorStep
|
||||
|
||||
## DataProcessorPipeline
|
||||
|
||||
[[autodoc]] lerobot.processor.pipeline.DataProcessorPipeline
|
||||
|
||||
## PolicyProcessorPipeline
|
||||
|
||||
[[autodoc]] lerobot.processor.pipeline.PolicyProcessorPipeline
|
||||
@@ -1,147 +0,0 @@
|
||||
# Robots
|
||||
|
||||
Every robot in LeRobot implements the [`Robot`] interface: connect, read an observation, send an action,
|
||||
disconnect. Writing a policy or a recording script against that interface means it works with any supported
|
||||
arm without change.
|
||||
|
||||
This page is the generated reference. For wiring, calibration and first-run instructions, start with the
|
||||
hardware guides — [SO-101](../so101), [LeKiwi](../lekiwi), [Hope Jr](../hope_jr), [Reachy 2](../reachy2),
|
||||
[OpenArm](../openarm) — or [Imitation Learning for Robots](../il_robots) for the end-to-end workflow. To add
|
||||
a robot of your own, see [Bring Your Own Hardware](../integrate_hardware).
|
||||
|
||||
## Robot
|
||||
|
||||
The abstract base class. Subclasses implement every method below; the contract described here is what a
|
||||
policy or recording loop can rely on.
|
||||
|
||||
[[autodoc]] lerobot.robots.Robot
|
||||
- connect
|
||||
- disconnect
|
||||
- configure
|
||||
- calibrate
|
||||
- get_observation
|
||||
- send_action
|
||||
- observation_features
|
||||
- action_features
|
||||
- is_connected
|
||||
- is_calibrated
|
||||
|
||||
## RobotConfig
|
||||
|
||||
[[autodoc]] lerobot.robots.RobotConfig
|
||||
|
||||
## make_robot_from_config
|
||||
|
||||
[[autodoc]] lerobot.robots.make_robot_from_config
|
||||
|
||||
## SO-100 and SO-101 followers
|
||||
|
||||
`SO100Follower` and `SO101Follower` are aliases of the same `SOFollower` class; the two arms differ in their
|
||||
configuration, not their control code. `SO100FollowerConfig` and `SO101FollowerConfig` are likewise aliases
|
||||
of `SOFollowerRobotConfig`.
|
||||
|
||||
[[autodoc]] lerobot.robots.so_follower.SOFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.so_follower.SOFollowerRobotConfig
|
||||
|
||||
## BiSOFollower
|
||||
|
||||
Two SO followers driven as one bimanual robot.
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_so_follower.BiSOFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_so_follower.BiSOFollowerConfig
|
||||
|
||||
## KochFollower
|
||||
|
||||
[[autodoc]] lerobot.robots.koch_follower.KochFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.koch_follower.KochFollowerConfig
|
||||
|
||||
## LeKiwi
|
||||
|
||||
`LeKiwi` runs on the robot itself. `LeKiwiClient` is the host-side proxy that talks to it over the network
|
||||
and presents the same [`Robot`] interface.
|
||||
|
||||
[[autodoc]] lerobot.robots.lekiwi.LeKiwi
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.lekiwi.LeKiwiConfig
|
||||
|
||||
[[autodoc]] lerobot.robots.lekiwi.LeKiwiClient
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.lekiwi.LeKiwiClientConfig
|
||||
|
||||
## OpenArmFollower
|
||||
|
||||
[[autodoc]] lerobot.robots.openarm_follower.OpenArmFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.openarm_follower.OpenArmFollowerConfig
|
||||
|
||||
## BiOpenArmFollower
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_openarm_follower.BiOpenArmFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_openarm_follower.BiOpenArmFollowerConfig
|
||||
|
||||
## OmxFollower
|
||||
|
||||
[[autodoc]] lerobot.robots.omx_follower.OmxFollower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.omx_follower.OmxFollowerConfig
|
||||
|
||||
## Reachy2Robot
|
||||
|
||||
[[autodoc]] lerobot.robots.reachy2.Reachy2Robot
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.reachy2.Reachy2RobotConfig
|
||||
|
||||
## UnitreeG1
|
||||
|
||||
[[autodoc]] lerobot.robots.unitree_g1.UnitreeG1
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.unitree_g1.UnitreeG1Config
|
||||
|
||||
## Hope Jr
|
||||
|
||||
The Hope Jr humanoid is exposed as two independent robots, an arm and a hand.
|
||||
|
||||
[[autodoc]] lerobot.robots.hope_jr.HopeJrArm
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.hope_jr.HopeJrArmConfig
|
||||
|
||||
[[autodoc]] lerobot.robots.hope_jr.HopeJrHand
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.hope_jr.HopeJrHandConfig
|
||||
|
||||
## RebotB601Follower
|
||||
|
||||
[[autodoc]] lerobot.robots.rebot_b601_follower.RebotB601Follower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.rebot_b601_follower.RebotB601FollowerRobotConfig
|
||||
|
||||
## BiRebotB601Follower
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_rebot_b601_follower.BiRebotB601Follower
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.bi_rebot_b601_follower.BiRebotB601FollowerConfig
|
||||
|
||||
## EarthRoverMiniPlus
|
||||
|
||||
[[autodoc]] lerobot.robots.earthrover_mini_plus.EarthRoverMiniPlus
|
||||
- all
|
||||
|
||||
[[autodoc]] lerobot.robots.earthrover_mini_plus.EarthRoverMiniPlusConfig
|
||||
@@ -1,30 +0,0 @@
|
||||
# Teleoperators
|
||||
|
||||
A teleoperator produces actions for a robot to follow — a leader arm, a gamepad, a keyboard, a phone. All of
|
||||
them implement the [`Teleoperator`] interface, so a recording script written against it works with any input
|
||||
device.
|
||||
|
||||
See [Phone teleoperation](../phone_teleop) and [Isaac Teleop](../isaac_teleop) for setup guides, and
|
||||
[Imitation Learning for Robots](../il_robots) for the recording workflow.
|
||||
|
||||
## Teleoperator
|
||||
|
||||
[[autodoc]] lerobot.teleoperators.Teleoperator
|
||||
- connect
|
||||
- disconnect
|
||||
- configure
|
||||
- calibrate
|
||||
- get_action
|
||||
- send_feedback
|
||||
- action_features
|
||||
- feedback_features
|
||||
- is_connected
|
||||
- is_calibrated
|
||||
|
||||
## TeleoperatorConfig
|
||||
|
||||
[[autodoc]] lerobot.teleoperators.TeleoperatorConfig
|
||||
|
||||
## make_teleoperator_from_config
|
||||
|
||||
[[autodoc]] lerobot.teleoperators.make_teleoperator_from_config
|
||||
@@ -62,7 +62,10 @@ Reference data points on a 4×H100 80 GB cluster (`accelerate launch --num_proce
|
||||
| `smolvla` | 27m 49s | 0.312 | 0.011 | ~80% | `--policy.path=lerobot/smolvla_base`, `freeze_vision_encoder=false`, `train_expert_only=false` |
|
||||
| `pi05` | 3h 41m | 2.548 | 0.014 | ~95% | `--policy.pretrained_path=lerobot/pi05_base`, `gradient_checkpointing=true`, `dtype=bfloat16`, vision encoder + expert trained |
|
||||
|
||||
The `dataloading_s` vs. `update_s` ratio is the diagnostic that matters: when `dataloading_s` approaches `update_s`, more GPUs stop helping — your dataloader is the bottleneck and you should look at `--num_workers`, image resolution, and disk speed before adding compute.
|
||||
Training logs separate the full iteration into `dataloading_s` (`next(dl_iter)`), `preprocessing_s`
|
||||
(image conversion and the policy pipeline), and `update_s` (the optimizer update). `step_s` covers all
|
||||
three and drives `samples_per_s`. The benchmark above predates this split, so its `dataloading_s` includes
|
||||
preprocessing.
|
||||
|
||||
### Schedule and checkpoints
|
||||
|
||||
|
||||
+121
-16
@@ -241,24 +241,129 @@ See the [Real-Time Chunking](./rtc) guide for details on tuning RTC parameters.
|
||||
|
||||
---
|
||||
|
||||
## Interactive Sessions
|
||||
|
||||
Add `--interactive=true` to drive the rollout from the terminal instead of starting immediately. Hardware connects and the policy loads as usual, but **the robot stays still until you type `/start`** — useful when you want to position the scene first, re-instruct the policy between attempts, or run several takes without paying the load time again.
|
||||
|
||||
```bash
|
||||
lerobot-rollout \
|
||||
--strategy.type=base \
|
||||
--policy.path=${HF_USER}/my_smolvla_policy \
|
||||
--robot.type=so100_follower \
|
||||
--robot.port=/dev/ttyACM0 \
|
||||
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
|
||||
--task="pick up the cube" \
|
||||
--interactive=true
|
||||
```
|
||||
|
||||
| Command | Action |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `/start` | Start (or restart) the policy control loop |
|
||||
| `/subtask <text>` | Change the instruction the policy follows, without stopping. No argument prints the current task. Only affects policies that condition on language (SmolVLA, π0/π0.5, and similar) |
|
||||
| `/reset` | Stop movement, return the robot to its startup position, and restore the `--task` instruction |
|
||||
| `/stop` | End the session and run the normal shutdown routines |
|
||||
| `/help` | List the commands |
|
||||
|
||||
```text
|
||||
> /start
|
||||
Rollout running — task 'pick up the cube'. /subtask <text> to change it, ...
|
||||
> /subtask put the cube in the box
|
||||
Task: 'pick up the cube' → 'put the cube in the box' (applies from the next policy inference)
|
||||
> /reset
|
||||
Task restored to 'pick up the cube'
|
||||
Resetting — returning the robot to its initial position...
|
||||
Robot reset — holding at initial position. /start to run.
|
||||
> /stop
|
||||
```
|
||||
|
||||
`Ctrl-C` still shuts down as usual, and closing stdin (`Ctrl-D`, or the end of a piped script) ends the session — so a piped script must keep stdin open for the intended duration:
|
||||
|
||||
```bash
|
||||
(printf '/start\n'; sleep 60; printf '/stop\n') | lerobot-rollout ... --interactive=true
|
||||
```
|
||||
|
||||
**How `/subtask` reaches the policy.** The stdin reader publishes the new instruction to the inference engine, which picks it up on its own inference thread, so nothing is mutated across threads while the robot is moving. How quickly the behavior changes depends on the backend:
|
||||
|
||||
- **Sync** (`--inference.type=sync`) — precomputed chunk actions are dropped, so the new instruction applies on the very next control tick. Without this a chunking policy would keep executing up to `chunk_size` stale actions (seconds of the old behavior). Only the queued actions are discarded, so observation history and the rest of the episode state are preserved.
|
||||
- **RTC** (`--inference.type=rtc`) — the next chunk is generated under the new instruction and merged over the previous chunk's leftover prefix, so the switch lands within one inference and the motion stays continuous. The queue is deliberately not cleared: that would leave the robot without commands for a full inference latency. (With blending turned off via `--inference.rtc.enabled=false` the queued chunk drains first, so the switch lands up to one chunk later.)
|
||||
|
||||
With `--use_torch_compile=true`, a switch whose instruction tokenizes to a different length can trigger a recompilation on the next forward pass, pausing inference for as long as the original warm-up took. Prefer leaving compilation off for sessions where you expect to re-instruct the policy often.
|
||||
|
||||
**Logs below ERROR are muted while the session runs** so routine output doesn't interleave with what you're typing; errors and fatal inference failures still show, and normal logging resumes when the session ends. The gate is process-wide (it also withholds INFO/WARNING from any file handler you attached for the duration). Run without `--interactive` to watch the live log.
|
||||
|
||||
Sessions work over SSH and on headless machines — the command reader uses the terminal (or pipe) directly and needs no display server.
|
||||
|
||||
**Recording while interactive.** `--strategy.type=sentry` also supports `--interactive=true`: the session records continuously while you steer it. Each `/start`…`/reset` segment saves complete episodes plus one final partial episode, the dataset stays open until shutdown, and **frames are labeled with the live task** — a `/subtask` changes both the policy conditioning and the recorded label from the same frame onwards.
|
||||
|
||||
```bash
|
||||
lerobot-rollout \
|
||||
--strategy.type=sentry \
|
||||
--policy.path=${HF_USER}/my_smolvla_policy \
|
||||
--robot.type=so100_follower \
|
||||
--robot.port=/dev/ttyACM0 \
|
||||
--dataset.repo_id=${HF_USER}/rollout_cube_sessions \
|
||||
--task="pick up the cube" \
|
||||
--interactive=true
|
||||
```
|
||||
|
||||
The other recording strategies (episodic, DAgger, highlight) are not supported: they bind their own keyboard controls, which would compete with the command prompt for the same terminal.
|
||||
|
||||
### Programmatic control
|
||||
|
||||
Everything the CLI session does is available as a library API: `RolloutController` exposes thread-safe `start()` / `reset()` / `stop()` / `set_task()` methods plus a `RolloutEvent` callback, with no stdin, printing, or log muting attached — embed it in your own application, network server, or notebook:
|
||||
|
||||
```python
|
||||
from threading import Event, Thread
|
||||
|
||||
from lerobot.rollout import (
|
||||
LinkedEvent,
|
||||
RolloutController,
|
||||
RolloutEvent,
|
||||
build_rollout_context,
|
||||
create_strategy,
|
||||
)
|
||||
|
||||
parent = Event() # your application's shutdown signal
|
||||
ctx = build_rollout_context(cfg, LinkedEvent(parent)) # loads policy, connects robot
|
||||
strategy = create_strategy(cfg.strategy)
|
||||
strategy.setup(ctx)
|
||||
|
||||
controller = RolloutController(strategy, ctx, on_event=print) # or your own observer
|
||||
serve_thread = Thread(target=controller.serve) # serve() blocks; run it where you like
|
||||
serve_thread.start()
|
||||
|
||||
controller.start() # robot starts executing the policy
|
||||
controller.set_task("grab the red cube") # re-instruct mid-run
|
||||
controller.reset() # stop movement, return home, stay warm
|
||||
controller.stop() # end serve()
|
||||
|
||||
serve_thread.join()
|
||||
strategy.teardown(ctx) # teardown stays with the caller
|
||||
```
|
||||
|
||||
Set `play_sounds=False` in the config unless you want the vocal announcements, and note that `build_rollout_context` requires the shutdown event to be a `LinkedEvent` (the controller ends run segments through its local flag; your `parent` event still forces a full shutdown). `InteractiveSession` itself is a thin front-end over this controller — commands map 1:1 onto its methods.
|
||||
|
||||
---
|
||||
|
||||
## Common Flags
|
||||
|
||||
| Flag | Description | Default |
|
||||
| --------------------------------- | ----------------------------------------------------------------- | ------- |
|
||||
| `--policy.path` | **Required.** HF Hub model ID or local checkpoint path | -- |
|
||||
| `--robot.type` | **Required.** Robot type (e.g. `so100_follower`, `koch_follower`) | -- |
|
||||
| `--robot.port` | Serial port for the robot | -- |
|
||||
| `--robot.cameras` | Camera configuration (JSON dict) | -- |
|
||||
| `--fps` | Control loop frequency | 30 |
|
||||
| `--duration` | Run time in seconds (0 = infinite) | 0 |
|
||||
| `--device` | Torch device (`cpu`, `cuda`, `mps`) | auto |
|
||||
| `--task` | Task description (used when no dataset is provided) | -- |
|
||||
| `--display_data` | Stream telemetry to Rerun visualization | false |
|
||||
| `--display_ip` / `--display_port` | Remote Rerun server address | -- |
|
||||
| `--interpolation_multiplier` | Action interpolation factor | 1 |
|
||||
| `--use_torch_compile` | Enable `torch.compile` for inference | false |
|
||||
| `--resume` | Resume a previous recording session | false |
|
||||
| `--play_sounds` | Vocal synthesis for events | true |
|
||||
| Flag | Description | Default |
|
||||
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `--policy.path` | **Required.** HF Hub model ID or local checkpoint path | -- |
|
||||
| `--robot.type` | **Required.** Robot type (e.g. `so100_follower`, `koch_follower`) | -- |
|
||||
| `--robot.port` | Serial port for the robot | -- |
|
||||
| `--robot.cameras` | Camera configuration (JSON dict) | -- |
|
||||
| `--fps` | Control loop frequency | 30 |
|
||||
| `--duration` | Run time in seconds (0 = infinite) | 0 |
|
||||
| `--device` | Torch device (`cpu`, `cuda`, `mps`) | auto |
|
||||
| `--task` | Task description (used when no dataset is provided) | -- |
|
||||
| `--display_data` | Stream telemetry to Rerun visualization | false |
|
||||
| `--display_ip` / `--display_port` | Remote Rerun server address | -- |
|
||||
| `--interpolation_multiplier` | Action interpolation factor | 1 |
|
||||
| `--interactive` | Chat-style stdin session (see [Interactive Sessions](#interactive-sessions)); the robot stays idle until `/start`. Base and sentry strategies | false |
|
||||
| `--use_torch_compile` | Enable `torch.compile` for inference | false |
|
||||
| `--resume` | Resume a previous recording session | false |
|
||||
| `--play_sounds` | Vocal synthesis for events | true |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -242,6 +242,17 @@ python src/lerobot/scripts/augment_dataset_quantile_stats.py \
|
||||
--repo-id=your_dataset
|
||||
```
|
||||
|
||||
Recording, resuming, and merging aggregate quantiles from per-episode summaries, so `meta/stats.json` ends up holding a conservative envelope (`min` for `q <= 50`, `max` for `q > 50`) rather than whole-dataset quantiles. To estimate the latter, scan every episode with a running histogram:
|
||||
|
||||
```bash
|
||||
python src/lerobot/scripts/augment_dataset_quantile_stats.py \
|
||||
--repo-id=your_dataset \
|
||||
--overwrite \
|
||||
--skip-images
|
||||
```
|
||||
|
||||
`--skip-images` keeps the existing image statistics and avoids video decoding when only `STATE`/`ACTION` need recomputing, and `--root` reads a local dataset instead of the Hub. These values are histogram estimates, subject to discretization and rebinning error, so they can differ from the conservative ones — which changes MolmoAct2's normalized targets and therefore its loss scale. Statistics already saved inside an existing checkpoint are not affected.
|
||||
|
||||
Alternatively, train MolmoAct2 with mean/std normalization:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -127,6 +127,17 @@ lerobot-edit-dataset \
|
||||
|
||||
Or keep the dataset as-is and pass `--policy.normalization_mapping='{"ACTION": "MEAN_STD", "STATE": "MEAN_STD", "VISUAL": "IDENTITY"}'`.
|
||||
|
||||
Recording, resuming, and merging aggregate quantiles from per-episode summaries, so `meta/stats.json` ends up holding a conservative envelope (`min` for `q <= 50`, `max` for `q > 50`) rather than whole-dataset quantiles. To estimate the latter, scan every episode with a running histogram:
|
||||
|
||||
```bash
|
||||
python src/lerobot/scripts/augment_dataset_quantile_stats.py \
|
||||
--repo-id=your_dataset \
|
||||
--overwrite \
|
||||
--skip-images
|
||||
```
|
||||
|
||||
`--skip-images` keeps the existing image statistics and avoids video decoding when only `STATE`/`ACTION` need recomputing, and `--root` reads a local dataset instead of the Hub. These values are histogram estimates, subject to discretization and rebinning error, so they can differ from the conservative ones — which changes π₀.₅'s normalized targets and therefore its loss scale. Statistics already saved inside an existing checkpoint are not affected.
|
||||
|
||||
### Training Command Example
|
||||
|
||||
The same finetune with the VLM frozen: less memory, at some cost in success rate. Swap `--dataset.repo_id` for your own dataset.
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
|
||||
https://diffusion-policy.cs.columbia.edu
|
||||
|
||||
## Training
|
||||
|
||||
The reference implementation maintains an exponential moving average (EMA) of the policy weights during training and evaluates the EMA weights. To reproduce this behavior, enable the trainer's EMA shadow:
|
||||
|
||||
```bash
|
||||
lerobot-train \
|
||||
--policy.type=diffusion \
|
||||
--ema.enable=true \
|
||||
...
|
||||
```
|
||||
|
||||
Checkpoints then contain a directly loadable copy of the EMA weights next to the live ones, e.g. for evaluation:
|
||||
|
||||
```bash
|
||||
lerobot-eval --policy.path=outputs/train/.../checkpoints/last/pretrained_model_ema ...
|
||||
```
|
||||
|
||||
The EMA decay schedule (`--ema.inv_gamma`, `--ema.power`, ...) defaults to the reference implementation's values. For a constant decay instead of the warmup schedule (e.g. to match openpi's pi0/pi05 training), set `--ema.decay=0.99`.
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
|
||||
@@ -59,6 +59,22 @@ When `use_relative_actions=true`, the training script automatically:
|
||||
|
||||
---
|
||||
|
||||
## EMA of the policy weights
|
||||
|
||||
OpenPI maintains an exponential moving average of the weights during training (`ema_decay=0.99` by default) and keeps the EMA copy for inference. To reproduce this with the LeRobot trainer, enable the EMA shadow with a constant decay:
|
||||
|
||||
```bash
|
||||
python -m lerobot.scripts.lerobot_train \
|
||||
--policy.type=pi05 \
|
||||
--dataset.repo_id=your_org/your_dataset \
|
||||
--ema.enable=true \
|
||||
--ema.decay=0.99
|
||||
```
|
||||
|
||||
Checkpoints then contain a directly loadable copy of the EMA weights in `pretrained_model_ema/` next to the live ones. Note that the shadow is a full extra copy of the parameters on the GPU. Like OpenPI (which disables EMA in its LoRA configs), EMA is not supported together with PEFT adapters.
|
||||
|
||||
---
|
||||
|
||||
## Citation
|
||||
|
||||
If you use this work, please cite both **OpenPI** and the π₀.₅ paper:
|
||||
|
||||
@@ -1,287 +0,0 @@
|
||||
# Writing docstrings
|
||||
|
||||
LeRobot's API reference is generated directly from the docstrings in `src/lerobot/`. A docstring is not a
|
||||
comment — it is the published documentation for that object, and the format below is what the renderer and
|
||||
the CI checks parse.
|
||||
|
||||
This page is the contract. If you are adding or editing anything public in `src/lerobot/`, follow it.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **An undocumented public method is an invisible one.** `[[autodoc]]` silently skips members that have no
|
||||
> docstring — no warning, no error, it simply does not appear on the rendered page. Coverage and
|
||||
> API-reference completeness are the same problem.
|
||||
|
||||
## The format in one example
|
||||
|
||||
Google section headers, Hugging Face type formatting. Both, not one or the other.
|
||||
|
||||
````python
|
||||
def send_action(self, action: RobotAction, rate_hz: float = 30.0) -> RobotAction:
|
||||
"""Command the robot to move to a target joint configuration.
|
||||
|
||||
Values are clipped by the configured maximum relative target before reaching the motors, so the
|
||||
returned action may differ from the requested one.
|
||||
|
||||
Args:
|
||||
action (`dict[str, float]`):
|
||||
Target values keyed by motor name, e.g. `{"shoulder_pan.pos": 0.0}`. Keys must match the
|
||||
robot's action features.
|
||||
rate_hz (`float`, *optional*, defaults to `30.0`):
|
||||
Control loop frequency.
|
||||
|
||||
Returns:
|
||||
`dict[str, float]`: The action actually written to the motors after safety clipping.
|
||||
|
||||
Raises:
|
||||
DeviceNotConnectedError: If the robot has not been connected.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig
|
||||
>>> robot = SO101Follower(SO101FollowerConfig(port="/dev/ttyACM0")) # doctest: +SKIP
|
||||
>>> robot.connect() # doctest: +SKIP
|
||||
>>> robot.send_action({"shoulder_pan.pos": 0.0}) # doctest: +SKIP
|
||||
```
|
||||
"""
|
||||
````
|
||||
|
||||
Cross-references are omitted from the examples on this page — see [Cross-references](#cross-references) for
|
||||
their syntax and why they cannot be shown inside a code block.
|
||||
|
||||
## Rules
|
||||
|
||||
### Sections
|
||||
|
||||
`Args:` · `Returns:` · `Raises:` · `Yields:` · `Example:` · `Note:`
|
||||
|
||||
In that order. No other section headers. A one-line summary comes first, then an optional free-form
|
||||
description, then the sections.
|
||||
|
||||
### The `Args:` line is machine-parsed
|
||||
|
||||
```
|
||||
name (`type`, *optional*, defaults to `X`):
|
||||
Description, indented on its own line.
|
||||
```
|
||||
|
||||
The `*optional*, defaults to` clause is **checked against the real signature default** by
|
||||
`make check-docstrings`. It is not decorative — if you write a default that has drifted from the code, CI
|
||||
fails. Omit the clause entirely for required parameters:
|
||||
|
||||
```python
|
||||
Args:
|
||||
port (`str`):
|
||||
Serial port the arm is connected to, e.g. `/dev/ttyACM0`.
|
||||
max_relative_target (`float | dict[str, float]`, *optional*):
|
||||
Caps the magnitude of the relative positional target vector. `None` disables clipping.
|
||||
use_degrees (`bool`, *optional*, defaults to `True`):
|
||||
Keep `True` for backward compatibility with existing policies and datasets.
|
||||
```
|
||||
|
||||
Types go in backticks. Use `*optional*` with no `defaults to` when the default is `None` or is otherwise not
|
||||
worth restating.
|
||||
|
||||
### `Returns:` is type-first
|
||||
|
||||
One indented line, type first, then a colon, then the description:
|
||||
|
||||
```python
|
||||
Returns:
|
||||
`dict[str, float]`: The action actually written to the motors after safety clipping.
|
||||
```
|
||||
|
||||
`Yields:` takes the same shape.
|
||||
|
||||
### `**Attributes**:`, never `Attributes:`
|
||||
|
||||
doc-builder parses a bare `Attributes:` as a **synonym for `Parameters:`**, so your attributes get rendered
|
||||
as constructor arguments. This is silent and wrong. Whenever the attributes differ from the constructor
|
||||
parameters, use the bold form with a `--` separator:
|
||||
|
||||
```python
|
||||
class Robot(abc.ABC):
|
||||
"""The base abstract class for all LeRobot-compatible robots.
|
||||
|
||||
**Attributes**:
|
||||
- **config_class** (`type[RobotConfig]`) -- The expected configuration class for this robot.
|
||||
- **name** (`str`) -- The unique robot name used to identify this robot type.
|
||||
"""
|
||||
```
|
||||
|
||||
Note `--`, not `:`.
|
||||
|
||||
### Cross-references
|
||||
|
||||
Use doc-builder's bracket syntax: a square-bracketed backtick-quoted path. **Sphinx roles (`:pymeth:`,
|
||||
`:pyattr:`) are not supported** and render as literal text on the page.
|
||||
|
||||
| Want | Write |
|
||||
| ---------------------------- | ----------------------------------- |
|
||||
| Class in the main package | [`Robot`] |
|
||||
| Method, show the full path | [`Robot.connect`] |
|
||||
| Method, show the bare name | [`~Robot.connect`] |
|
||||
| Nested path | [`~robots.Robot.connect`] |
|
||||
| Object in another HF library | [`~accelerate.Accelerator`] |
|
||||
|
||||
The `~` strips the path from the **link text only**; the link still resolves to the full path.
|
||||
|
||||
> [!NOTE]
|
||||
> doc-builder resolves this syntax everywhere in a page — including inside fenced code blocks. That is why
|
||||
> the docstring examples on this page use plain prose instead of cross-references: a code block containing
|
||||
> one would render the resolved link rather than the syntax you need to type. In your own docstrings, use
|
||||
> cross-references freely; this restriction only affects documentation _about_ the syntax.
|
||||
|
||||
### Callouts
|
||||
|
||||
Use GitHub-style blockquotes:
|
||||
|
||||
```markdown
|
||||
> [!TIP]
|
||||
> Call this once at startup — it takes about two seconds.
|
||||
|
||||
> [!WARNING]
|
||||
> Torque is disabled on disconnect. The arm will drop if it is holding a load.
|
||||
```
|
||||
|
||||
The `<Tip>` component is legacy per doc-builder; don't add new ones.
|
||||
|
||||
### Examples must be fenced
|
||||
|
||||
An example lives inside a fenced ` ```python ` block containing `>>> `. The fence is what makes it render
|
||||
as a code block, and it is what the doctest preprocessor's regex looks for:
|
||||
|
||||
````python
|
||||
Example:
|
||||
```python
|
||||
>>> from lerobot.robots.so_follower import SO101FollowerConfig
|
||||
>>> cfg = SO101FollowerConfig(port="/dev/ttyACM0")
|
||||
>>> cfg.use_degrees
|
||||
True
|
||||
```
|
||||
````
|
||||
|
||||
> [!WARNING]
|
||||
> An unfenced `>>>` is still collected — doctest finds prompts anywhere in a docstring. What you lose is the
|
||||
> rendering, so it shows up as a wall of prose on the page. Every example needs the fence.
|
||||
|
||||
Every example either executes in CI or carries `# doctest: +SKIP`. Anything that touches hardware, a GPU, or
|
||||
downloads from the Hub gets `+SKIP`:
|
||||
|
||||
````python
|
||||
Example:
|
||||
```python
|
||||
>>> robot.connect() # doctest: +SKIP
|
||||
>>> policy = ACTPolicy.from_pretrained("lerobot/act_aloha_sim_transfer_cube_human") # doctest: +SKIP
|
||||
```
|
||||
````
|
||||
|
||||
Add files containing runnable examples to `utils/documentation_tests.txt`.
|
||||
|
||||
Put examples on the three to five genuine entry points of a module. Examples on trivial accessors are noise.
|
||||
|
||||
## Three patterns you will hit constantly
|
||||
|
||||
### Config dataclasses
|
||||
|
||||
Configuration fields are historically documented with `#` comments above each field. **doc-builder cannot
|
||||
see inline comments** — such a class renders with every field listed and not a single description. Move them
|
||||
into an `Args:` block on the class docstring:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class SOFollowerConfig:
|
||||
"""Configuration for SO-family follower arms.
|
||||
|
||||
Args:
|
||||
port (`str`):
|
||||
Serial port the arm is connected to, e.g. `/dev/ttyACM0`.
|
||||
max_relative_target (`float | dict[str, float]`, *optional*):
|
||||
Caps the magnitude of the relative positional target vector. A scalar applies to all motors;
|
||||
a dict maps motor name to a per-motor cap. `None` disables clipping.
|
||||
use_degrees (`bool`, *optional*, defaults to `True`):
|
||||
Keep `True` for backward compatibility with existing policies and datasets.
|
||||
"""
|
||||
|
||||
port: str
|
||||
max_relative_target: float | dict[str, float] | None = None
|
||||
use_degrees: bool = True
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **doc-builder does not inherit docstrings from base classes.** LeRobot's registered config classes are
|
||||
> often thin multiple-inheritance shims:
|
||||
>
|
||||
> ```python
|
||||
> @RobotConfig.register_subclass("so101_follower")
|
||||
> @dataclass
|
||||
> class SOFollowerRobotConfig(RobotConfig, SOFollowerConfig):
|
||||
> pass
|
||||
> ```
|
||||
>
|
||||
> That class renders **every** field — including the ones it inherits — with no descriptions at all, no
|
||||
> matter how well the bases are documented. The `Args:` block must live on the concrete class that
|
||||
> `[[autodoc]]` names, and it must cover inherited fields too.
|
||||
|
||||
### Base class, then concrete subclass
|
||||
|
||||
The abstract base carries the canonical contract. Subclasses document only what deviates — port semantics,
|
||||
calibration quirks, motor layout, supported feature keys. Do not copy the base contract into every subclass.
|
||||
|
||||
`Robot`, `Teleoperator`, `Camera`, `MotorsBus`, `ProcessorStep`, and `PreTrainedPolicy` all follow this
|
||||
shape.
|
||||
|
||||
### Module-level aliases
|
||||
|
||||
Several public names are aliases rather than distinct classes:
|
||||
|
||||
```python
|
||||
SO100FollowerConfig = SOFollowerRobotConfig
|
||||
SO101FollowerConfig = SOFollowerRobotConfig
|
||||
```
|
||||
|
||||
`[[autodoc]]` resolves the alias and renders the **canonical** class name, so a `## SO101FollowerConfig`
|
||||
heading will show `class lerobot.robots.so_follower.SOFollowerRobotConfig` in the body. Document the
|
||||
canonical class once, and mention the aliases in the page's prose rather than giving each alias its own
|
||||
autodoc block.
|
||||
|
||||
## What not to document
|
||||
|
||||
- **Private members.** Anything starting with `_` is not part of the public API.
|
||||
- **The type annotation restated as prose.** `port (`str`): A string.` adds nothing. Say what it is for.
|
||||
- **Vendored upstream code.** `src/lerobot/policies/molmoact2/molmoact2_hf_model/` is vendored from
|
||||
`transformers` and already carries upstream-style docstrings. Leave it alone — restyling it only creates
|
||||
conflicts on the next sync. It is excluded from the API reference and from the docstring checks.
|
||||
|
||||
## How this is enforced
|
||||
|
||||
| Check | What it catches |
|
||||
| ------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
||||
| `make check-docstrings` | An `Args:` entry that doesn't match the signature; a documented default that has drifted from the real one |
|
||||
| `make doctest` | Examples that no longer run |
|
||||
| `make check-doctest-list` | Stale or unsorted entries in `utils/documentation_tests.txt` |
|
||||
| `ruff` (`D` rules) | Google-convention style violations |
|
||||
| `interrogate` | Docstring coverage falling below the current threshold |
|
||||
| doc-builder | A `[[autodoc]]` path that points at something that doesn't exist — this breaks the docs build |
|
||||
|
||||
Run them together before opening a PR:
|
||||
|
||||
```bash
|
||||
make check-docstrings && make doctest && pre-commit run --all-files
|
||||
```
|
||||
|
||||
Then render the page and actually look at it:
|
||||
|
||||
```bash
|
||||
doc-builder build lerobot docs/source/ --build_dir /tmp/doc-build
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Every public member you touched has a docstring.
|
||||
- [ ] Every `Args:` entry matches the signature, including the `*optional*, defaults to` clause.
|
||||
- [ ] `Returns:` is type-first on one indented line.
|
||||
- [ ] No bare `Attributes:` — use `**Attributes**:` with `--` separators.
|
||||
- [ ] No Sphinx roles — cross-references use [`~module.Class.method`].
|
||||
- [ ] Examples are inside a fenced ` ```python ` block, and either run in CI or carry `# doctest: +SKIP`.
|
||||
- [ ] Config dataclass fields are in an `Args:` block on the concrete class, not `#` comments.
|
||||
- [ ] The rendered page has been eyeballed.
|
||||
+17
-102
@@ -401,101 +401,19 @@ exclude = ["tests/artifacts/**/*.safetensors", "*_pb2.py", "*_pb2_grpc.py"]
|
||||
# N: pep8-naming
|
||||
# TODO: Uncomment rules when ready to use
|
||||
select = [
|
||||
"E", "W", "F", "I", "B", "C4", "T20", "N", "UP", "SIM", "D" #, "A", "S", "RUF"
|
||||
"E", "W", "F", "I", "B", "C4", "T20", "N", "UP", "SIM" #, "A", "S", "D", "RUF"
|
||||
]
|
||||
ignore = [
|
||||
"E501", # Line too long
|
||||
"T201", # Print statement found
|
||||
"T203", # Pprint statement found
|
||||
"B008", # Perform function call in argument defaults
|
||||
# D100/D104: module- and package-level docstrings. The API reference is generated from class and
|
||||
# function docstrings; a banner at the top of every file and every __init__.py would not appear on any
|
||||
# rendered page. Coverage of the things that do get rendered is enforced by interrogate instead.
|
||||
"D100",
|
||||
"D104",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = ["F401", "F403", "E402", "D104"]
|
||||
"__init__.py" = ["F401", "F403", "E402"]
|
||||
# E402: conditional-import guards (TYPE_CHECKING / is_package_available) must precede the imports they protect
|
||||
"src/lerobot/scripts/convert_dataset_v21_to_v30.py" = ["E402"]
|
||||
|
||||
# D (pydocstyle) is enabled globally, but only holds for code that has been converted to the docstring
|
||||
# standard in docs/source/writing_docstrings.mdx. Every module below is still on the old style; each entry
|
||||
# is deleted as that module is converted, and this block can be removed once it is empty.
|
||||
#
|
||||
# Not part of the API reference and not planned for conversion: tests, examples, benchmarks, templates,
|
||||
# CI helper scripts and the packaging shim.
|
||||
"tests/**" = ["D"]
|
||||
"examples/**" = ["D"]
|
||||
"benchmarks/**" = ["D"]
|
||||
"scripts/**" = ["D"]
|
||||
"setup.py" = ["D"]
|
||||
"src/lerobot/templates/**" = ["D"]
|
||||
# Vendored from transformers; keeps its upstream docstring style so syncs stay clean.
|
||||
"src/lerobot/policies/molmoact2/molmoact2_hf_model/**" = ["D"]
|
||||
# Awaiting conversion, one PR per module.
|
||||
"src/lerobot/annotations/**" = ["D"]
|
||||
"src/lerobot/async_inference/**" = ["D"]
|
||||
"src/lerobot/cameras/**" = ["D"]
|
||||
"src/lerobot/common/**" = ["D"]
|
||||
"src/lerobot/configs/**" = ["D"]
|
||||
"src/lerobot/data_processing/**" = ["D"]
|
||||
"src/lerobot/datasets/**" = ["D"]
|
||||
"src/lerobot/distributed/**" = ["D"]
|
||||
"src/lerobot/envs/**" = ["D"]
|
||||
"src/lerobot/jobs/**" = ["D"]
|
||||
"src/lerobot/model/**" = ["D"]
|
||||
"src/lerobot/motors/**" = ["D"]
|
||||
"src/lerobot/optim/**" = ["D"]
|
||||
"src/lerobot/policies/__init__.py" = ["D"]
|
||||
"src/lerobot/policies/pi_gemma.py" = ["D"]
|
||||
"src/lerobot/policies/common/**" = ["D"]
|
||||
# Wave 3 of the docstring initiative documents each policy family's config class in full, plus only
|
||||
# the public forward/select_action surface of modeling_*.py's main <Family>Policy class and the
|
||||
# processor_*.py's make_<family>_pre_post_processors factory. modeling_*.py and processor_*.py also
|
||||
# contain internal building blocks (nn.Module helpers, ProcessorStep internals) that remain out of
|
||||
# scope, so those two file patterns stay D-ignored wholesale rather than enumerated per symbol; the
|
||||
# narrower Policy/processor-factory scope is instead enforced via the AST coverage check and
|
||||
# utils/check_docstrings.py's leaf-module entries. configuration_*.py is fully documented and stays
|
||||
# checked here.
|
||||
"src/lerobot/policies/*/modeling_*.py" = ["D"]
|
||||
"src/lerobot/policies/*/processor_*.py" = ["D"]
|
||||
"src/lerobot/policies/evo1/evo1_model.py" = ["D"]
|
||||
"src/lerobot/policies/evo1/flow_matching.py" = ["D"]
|
||||
"src/lerobot/policies/evo1/internvl3_embedder.py" = ["D"]
|
||||
"src/lerobot/policies/fastwam/wan/**" = ["D"]
|
||||
"src/lerobot/policies/groot/action_head/**" = ["D"]
|
||||
"src/lerobot/policies/groot/groot_n1_7.py" = ["D"]
|
||||
"src/lerobot/policies/groot/utils.py" = ["D"]
|
||||
"src/lerobot/policies/lingbot_va/utils.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/action_interpolator.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/action_queue.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/debug_tracker.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/debug_visualizer.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/latency_tracker.py" = ["D"]
|
||||
"src/lerobot/policies/rtc/relative.py" = ["D"]
|
||||
"src/lerobot/policies/smolvla/smolvlm_with_expert.py" = ["D"]
|
||||
"src/lerobot/policies/vla_jepa/action_head.py" = ["D"]
|
||||
"src/lerobot/policies/vla_jepa/qwen_interface.py" = ["D"]
|
||||
"src/lerobot/policies/vla_jepa/world_model.py" = ["D"]
|
||||
"src/lerobot/policies/vqbet/vqbet_utils.py" = ["D"]
|
||||
"src/lerobot/policies/wall_x/constant.py" = ["D"]
|
||||
"src/lerobot/policies/wall_x/qwen_model/**" = ["D"]
|
||||
"src/lerobot/policies/wall_x/utils.py" = ["D"]
|
||||
"src/lerobot/policies/xvla/action_hub.py" = ["D"]
|
||||
"src/lerobot/policies/xvla/soft_transformer.py" = ["D"]
|
||||
"src/lerobot/policies/xvla/utils.py" = ["D"]
|
||||
"src/lerobot/processor/**" = ["D"]
|
||||
"src/lerobot/rewards/**" = ["D"]
|
||||
"src/lerobot/rl/**" = ["D"]
|
||||
"src/lerobot/rollout/**" = ["D"]
|
||||
"src/lerobot/scripts/**" = ["D"]
|
||||
"src/lerobot/teleoperators/**" = ["D"]
|
||||
"src/lerobot/transforms/**" = ["D"]
|
||||
"src/lerobot/transport/**" = ["D"]
|
||||
"src/lerobot/utils/**" = ["D"]
|
||||
"src/lerobot/lerobot_types.py" = ["D"]
|
||||
[tool.ruff.lint.isort]
|
||||
combine-as-imports = true
|
||||
known-first-party = ["lerobot"]
|
||||
@@ -539,24 +457,21 @@ default.extend-ignore-identifiers-re = [
|
||||
"seperated_timestep",
|
||||
]
|
||||
|
||||
# Docstring coverage gate. `fail-under` is a RATCHET, not a target: it is set just below the currently
|
||||
# measured coverage so it passes today, and is raised in the same PR that documents a module. Never set it
|
||||
# to a value that fails on main. The destination is 100; see docs/source/writing_docstrings.mdx.
|
||||
[tool.interrogate]
|
||||
ignore-init-module = true
|
||||
ignore-init-method = true
|
||||
ignore-nested-functions = false
|
||||
ignore-magic = false
|
||||
ignore-semiprivate = false
|
||||
ignore-private = false
|
||||
ignore-property-decorators = false
|
||||
ignore-module = false
|
||||
ignore-setters = false
|
||||
fail-under = 58
|
||||
output-format = "term-missing"
|
||||
color = true
|
||||
paths = ["src/lerobot"]
|
||||
exclude = ["src/lerobot/policies/molmoact2/molmoact2_hf_model"]
|
||||
# TODO: Uncomment when ready to use
|
||||
# [tool.interrogate]
|
||||
# ignore-init-module = true
|
||||
# ignore-init-method = true
|
||||
# ignore-nested-functions = false
|
||||
# ignore-magic = false
|
||||
# ignore-semiprivate = false
|
||||
# ignore-private = false
|
||||
# ignore-property-decorators = false
|
||||
# ignore-module = false
|
||||
# ignore-setters = false
|
||||
# fail-under = 80
|
||||
# output-format = "term-missing"
|
||||
# color = true
|
||||
# paths = ["src/lerobot"]
|
||||
|
||||
# TODO: Enable mypy gradually module by module across multiple PRs
|
||||
# Uncomment [tool.mypy] first, then uncomment individual module overrides as they get proper type annotations
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""LeRobot -- PyTorch library for real-world robotics.
|
||||
"""
|
||||
LeRobot -- PyTorch library for real-world robotics.
|
||||
|
||||
Provides datasets, pretrained policies, and tools for training, evaluation,
|
||||
data collection, and robot control. Integrates with Hugging Face Hub for
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""To enable `lerobot.__version__`."""
|
||||
"""To enable `lerobot.__version__`"""
|
||||
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ class Camera(abc.ABC):
|
||||
- Connection/disconnection
|
||||
- Frame capture (sync/async/latest)
|
||||
|
||||
**Attributes**:
|
||||
- **fps** (`int | None`) -- Configured frames per second.
|
||||
- **width** (`int | None`) -- Frame width in pixels.
|
||||
- **height** (`int | None`) -- Frame height in pixels.
|
||||
Attributes:
|
||||
fps (int | None): Configured frames per second
|
||||
width (int | None): Frame width in pixels
|
||||
height (int | None): Frame height in pixels
|
||||
"""
|
||||
|
||||
def __init__(self, config: CameraConfig):
|
||||
|
||||
@@ -40,20 +40,17 @@ class OpenCVCameraConfig(CameraConfig):
|
||||
OpenCVCameraConfig(0, 30, 1280, 720, fourcc="YUYV") # With YUYV format
|
||||
```
|
||||
|
||||
**Attributes**:
|
||||
- **index_or_path** (`int | Path`) -- Either an integer representing the camera device index, or a
|
||||
Path object pointing to a video file.
|
||||
- **fps** -- Requested frames per second for the color stream.
|
||||
- **width** -- Requested frame width in pixels for the color stream.
|
||||
- **height** -- Requested frame height in pixels for the color stream.
|
||||
- **color_mode** (`ColorMode`) -- Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
- **rotation** (`Cv2Rotation`) -- Image rotation setting (0°, 90°, 180°, or 270°). Defaults to no
|
||||
rotation.
|
||||
- **warmup_s** (`int`) -- Time reading frames before returning from connect (in seconds)
|
||||
- **fourcc** (`str | None`) -- FOURCC code for video format (e.g., "MJPG", "YUYV", "I420"). Defaults
|
||||
to None (auto-detect).
|
||||
- **backend** (`Cv2Backends`) -- OpenCV backend identifier
|
||||
(https://docs.opencv.org/3.4/d4/d15/group__videoio__flags__base.html). Defaults to ANY.
|
||||
Attributes:
|
||||
index_or_path: Either an integer representing the camera device index,
|
||||
or a Path object pointing to a video file.
|
||||
fps: Requested frames per second for the color stream.
|
||||
width: Requested frame width in pixels for the color stream.
|
||||
height: Requested frame height in pixels for the color stream.
|
||||
color_mode: Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
rotation: Image rotation setting (0°, 90°, 180°, or 270°). Defaults to no rotation.
|
||||
warmup_s: Time reading frames before returning from connect (in seconds)
|
||||
fourcc: FOURCC code for video format (e.g., "MJPG", "YUYV", "I420"). Defaults to None (auto-detect).
|
||||
backend: OpenCV backend identifier (https://docs.opencv.org/3.4/d4/d15/group__videoio__flags__base.html). Defaults to ANY.
|
||||
|
||||
Note:
|
||||
- Only 3-channel color output (RGB/BGR) is currently supported.
|
||||
|
||||
@@ -43,16 +43,16 @@ class Reachy2CameraConfig(CameraConfig):
|
||||
) # Left teleop camera, 640x480 @ 30FPS
|
||||
```
|
||||
|
||||
**Attributes**:
|
||||
- **name** (`str`) -- Name of the camera device. Can be "teleop" or "depth".
|
||||
- **image_type** (`str`) -- Type of image stream. For "teleop" camera, can be "left" or "right". For
|
||||
"depth" camera, can be "rgb" or "depth". (depth is not supported yet)
|
||||
- **fps** -- Requested frames per second for the color stream. Not configurable for Reachy 2 cameras.
|
||||
- **width** -- Requested frame width in pixels for the color stream.
|
||||
- **height** -- Requested frame height in pixels for the color stream.
|
||||
- **color_mode** (`ColorMode`) -- Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
- **ip_address** (`str | None`) -- IP address of the robot. Defaults to "localhost".
|
||||
- **port** (`int`) -- Port number for the camera server. Defaults to 50065.
|
||||
Attributes:
|
||||
name: Name of the camera device. Can be "teleop" or "depth".
|
||||
image_type: Type of image stream. For "teleop" camera, can be "left" or "right".
|
||||
For "depth" camera, can be "rgb" or "depth". (depth is not supported yet)
|
||||
fps: Requested frames per second for the color stream. Not configurable for Reachy 2 cameras.
|
||||
width: Requested frame width in pixels for the color stream.
|
||||
height: Requested frame height in pixels for the color stream.
|
||||
color_mode: Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
ip_address: IP address of the robot. Defaults to "localhost".
|
||||
port: Port number for the camera server. Defaults to 50065.
|
||||
|
||||
Note:
|
||||
- Only 3-channel color output (RGB/BGR) is currently supported.
|
||||
|
||||
@@ -36,28 +36,27 @@ class RealSenseCameraConfig(CameraConfig):
|
||||
RealSenseCameraConfig("0123456789", 30, 640, 480, rotation=Cv2Rotation.ROTATE_90) # With 90° rotation
|
||||
```
|
||||
|
||||
**Attributes**:
|
||||
- **fps** -- Requested frames per second for the color stream.
|
||||
- **width** -- Requested frame width in pixels for the color stream.
|
||||
- **height** -- Requested frame height in pixels for the color stream.
|
||||
- **serial_number_or_name** (`str`) -- Unique serial number or human-readable name to identify the
|
||||
camera.
|
||||
- **color_mode** (`ColorMode`) -- Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
- **use_rgb** (`bool`) -- Whether to enable the color stream. Defaults to True.
|
||||
- **use_depth** (`bool`) -- Whether to enable depth stream. Defaults to False.
|
||||
- **rotation** (`Cv2Rotation`) -- Image rotation setting (0°, 90°, 180°, or 270°). Defaults to no
|
||||
rotation.
|
||||
- **warmup_s** (`int`) -- Time reading frames before returning from connect (in seconds)
|
||||
- **exposure** (`int | None`) -- Manual exposure value for the color sensor. When set, auto-exposure
|
||||
is disabled and this fixed value is used. Valid ranges are camera-model specific and reported if the
|
||||
value is rejected. Defaults to None (leave unchanged).
|
||||
- **gain** (`int | None`) -- Manual gain value for the color sensor. When set, auto-exposure is
|
||||
disabled and this fixed gain is used, which also freezes exposure at its current value when no
|
||||
exposure is configured. Valid ranges are camera-model specific and reported if the value is
|
||||
rejected. Defaults to None (leave unchanged).
|
||||
- **white_balance** (`int | None`) -- Manual white balance value for the color sensor. When set, auto
|
||||
white balance is disabled and this fixed value is used. Valid ranges are camera-model specific and
|
||||
reported if the value is rejected. Defaults to None (leave unchanged).
|
||||
Attributes:
|
||||
fps: Requested frames per second for the color stream.
|
||||
width: Requested frame width in pixels for the color stream.
|
||||
height: Requested frame height in pixels for the color stream.
|
||||
serial_number_or_name: Unique serial number or human-readable name to identify the camera.
|
||||
color_mode: Color mode for image output (RGB or BGR). Defaults to RGB.
|
||||
use_rgb: Whether to enable the color stream. Defaults to True.
|
||||
use_depth: Whether to enable depth stream. Defaults to False.
|
||||
rotation: Image rotation setting (0°, 90°, 180°, or 270°). Defaults to no rotation.
|
||||
warmup_s: Time reading frames before returning from connect (in seconds)
|
||||
exposure: Manual exposure value for the color sensor. When set, auto-exposure is
|
||||
disabled and this fixed value is used. Valid ranges are camera-model specific
|
||||
and reported if the value is rejected. Defaults to None (leave unchanged).
|
||||
gain: Manual gain value for the color sensor. When set, auto-exposure is disabled
|
||||
and this fixed gain is used, which also freezes exposure at its current value
|
||||
when no exposure is configured. Valid ranges are camera-model specific and
|
||||
reported if the value is rejected. Defaults to None (leave unchanged).
|
||||
white_balance: Manual white balance value for the color sensor. When set, auto
|
||||
white balance is disabled and this fixed value is used. Valid ranges are
|
||||
camera-model specific and reported if the value is rejected. Defaults to None
|
||||
(leave unchanged).
|
||||
|
||||
Note:
|
||||
- Either name or serial_number must be specified.
|
||||
|
||||
@@ -22,7 +22,7 @@ Import them directly: ``from lerobot.configs.train import TrainPipelineConfig``
|
||||
"""
|
||||
|
||||
from .dataset import DatasetRecordConfig
|
||||
from .default import DatasetConfig, EvalConfig, JobConfig, PeftConfig, WandBConfig
|
||||
from .default import DatasetConfig, EMAConfig, EvalConfig, JobConfig, PeftConfig, WandBConfig
|
||||
from .policies import PreTrainedConfig
|
||||
from .recipe import MessageTurn, TrainingRecipe, load_recipe
|
||||
from .types import (
|
||||
@@ -57,6 +57,7 @@ __all__ = [
|
||||
# Config classes
|
||||
"DatasetRecordConfig",
|
||||
"DatasetConfig",
|
||||
"EMAConfig",
|
||||
"EvalConfig",
|
||||
"JobConfig",
|
||||
"MessageTurn",
|
||||
|
||||
@@ -139,6 +139,59 @@ class EvalConfig:
|
||||
return min(by_cpu, self.n_episodes, 64)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EMAConfig:
|
||||
"""Exponential moving average (EMA) of the policy weights.
|
||||
|
||||
Standard practice for diffusion-style policies (Chi et al. 2023, "Diffusion Policy", section V.D):
|
||||
the reference implementation enables it in every config and evaluates the EMA weights. Off by
|
||||
default here because it keeps a second full copy of the parameters in memory.
|
||||
|
||||
The decay follows the warmup schedule from diffusers' `EMAModel`:
|
||||
`decay_t = 1 - (1 + t / inv_gamma) ** -power`, clamped to `[min_decay, max_decay]`.
|
||||
The defaults mirror the reference implementation. Alternatively, set `decay` for a constant
|
||||
decay at every step, as used by openpi for pi0/pi05 (`ema_decay=0.99`).
|
||||
"""
|
||||
|
||||
enable: bool = False
|
||||
# Constant decay coefficient (openpi-style, e.g. 0.99 for pi0/pi05). When set, the warmup
|
||||
# schedule below is bypassed and the shadow uses this decay at every step.
|
||||
decay: float | None = None
|
||||
# Number of optimizer steps during which the shadow stays a hard copy of the live weights.
|
||||
update_after_step: int = 0
|
||||
# Warmup schedule parameters (see class docstring).
|
||||
inv_gamma: float = 1.0
|
||||
power: float = 0.75
|
||||
min_decay: float = 0.0
|
||||
max_decay: float = 0.9999
|
||||
# Evaluate the EMA weights (instead of the live ones) during periodic env eval.
|
||||
# Offline eval-loss (--eval_steps) always uses the live weights: it runs on every rank
|
||||
# while the EMA shadow only lives on the main process.
|
||||
use_for_eval: bool = True
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not (0.0 <= self.min_decay <= self.max_decay <= 1.0):
|
||||
raise ValueError(
|
||||
"Expected 0 <= ema.min_decay <= ema.max_decay <= 1, got "
|
||||
f"min_decay={self.min_decay} and max_decay={self.max_decay}."
|
||||
)
|
||||
if self.inv_gamma <= 0:
|
||||
raise ValueError(f"ema.inv_gamma must be positive, got {self.inv_gamma}.")
|
||||
if self.power <= 0:
|
||||
raise ValueError(f"ema.power must be positive, got {self.power}.")
|
||||
if self.update_after_step < 0:
|
||||
raise ValueError(f"ema.update_after_step must be >= 0, got {self.update_after_step}.")
|
||||
if self.decay is not None:
|
||||
if not 0.0 <= self.decay <= 1.0:
|
||||
raise ValueError(f"ema.decay must be in [0, 1], got {self.decay}.")
|
||||
# Keep the literals in sync with the field defaults above.
|
||||
if self.min_decay != 0.0 or self.max_decay != 0.9999:
|
||||
raise ValueError(
|
||||
"ema.decay (constant decay) and ema.min_decay/ema.max_decay (schedule clamp) are "
|
||||
"mutually exclusive: set one or the other."
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PeftConfig:
|
||||
# PEFT offers many fine-tuning methods, layer adapters being the most common and currently also the most
|
||||
|
||||
@@ -35,7 +35,7 @@ from lerobot.utils.hub import HubMixin, find_latest_hub_checkpoint
|
||||
from lerobot.utils.sample_weighting import SampleWeightingConfig
|
||||
|
||||
from . import parser
|
||||
from .default import DatasetConfig, EvalConfig, JobConfig, PeftConfig, WandBConfig
|
||||
from .default import DatasetConfig, EMAConfig, EvalConfig, JobConfig, PeftConfig, WandBConfig
|
||||
from .policies import PreTrainedConfig
|
||||
from .rewards import RewardModelConfig
|
||||
|
||||
@@ -163,6 +163,8 @@ class TrainPipelineConfig(HubMixin):
|
||||
# FSDP/DDP tuning knobs, compile & activation-checkpointing placeholders.
|
||||
accelerator: AcceleratorConfig = field(default_factory=AcceleratorConfig)
|
||||
eval: EvalConfig = field(default_factory=EvalConfig)
|
||||
# Maintain an EMA shadow of the policy weights during training (see EMAConfig).
|
||||
ema: EMAConfig = field(default_factory=EMAConfig)
|
||||
wandb: WandBConfig = field(default_factory=WandBConfig)
|
||||
peft: PeftConfig | None = None
|
||||
|
||||
|
||||
@@ -613,8 +613,15 @@ def aggregate_feature_stats(stats_ft_list: list[dict[str, dict]]) -> dict[str, d
|
||||
for q_key in quantile_keys:
|
||||
if all(q_key in s for s in stats_ft_list):
|
||||
quantile_values = np.stack([s[q_key] for s in stats_ft_list])
|
||||
weighted_quantiles = quantile_values * counts
|
||||
aggregated[q_key] = weighted_quantiles.sum(axis=0) / total_count
|
||||
# Exact global quantiles cannot be recovered from quantile summaries.
|
||||
# Keep a conservative envelope of the available estimates: min
|
||||
# for lower quantiles and max for upper quantiles. The resulting
|
||||
# values are bounds across the inputs, not global quantile estimates.
|
||||
q_percent = int(q_key[1:])
|
||||
if q_percent <= 50:
|
||||
aggregated[q_key] = np.min(quantile_values, axis=0)
|
||||
else:
|
||||
aggregated[q_key] = np.max(quantile_values, axis=0)
|
||||
|
||||
return aggregated
|
||||
|
||||
|
||||
@@ -33,11 +33,7 @@ if TYPE_CHECKING:
|
||||
from lerobot.policies.pretrained import PreTrainedPolicy
|
||||
|
||||
# Env vars through which `accelerate launch --config_file` (or a stray shell) would configure
|
||||
# accelerate behind the config system's back. Plugin `__post_init__`s read these silently as
|
||||
# field fallbacks (ACCELERATE_DYNAMO_* enables torch.compile through the default
|
||||
# TorchDynamoPlugin; ACCELERATE_GRADIENT_ACCUMULATION_STEPS overrides the explicitly passed
|
||||
# value inside Accelerator.__init__), which would make train_config.json lie about what ran.
|
||||
_ACCELERATE_ENV_PREFIXES = ("FSDP_", "PARALLELISM_CONFIG_", "ACCELERATE_DYNAMO_")
|
||||
# accelerate behind the config system's back, making train_config.json lie about what ran.
|
||||
_ACCELERATE_ENV_VARS = (
|
||||
"ACCELERATE_USE_FSDP",
|
||||
"ACCELERATE_USE_PARALLELISM_CONFIG",
|
||||
@@ -59,11 +55,7 @@ def guard_against_env_interference() -> None:
|
||||
"""
|
||||
if os.environ.get(_ENV_OVERRIDE):
|
||||
return
|
||||
offending = sorted(
|
||||
name
|
||||
for name in os.environ
|
||||
if name in _ACCELERATE_ENV_VARS or name.startswith(_ACCELERATE_ENV_PREFIXES)
|
||||
)
|
||||
offending = sorted(name for name in _ACCELERATE_ENV_VARS if name in os.environ)
|
||||
if offending:
|
||||
raise RuntimeError(
|
||||
f"Accelerate-configuring environment variables are set: {', '.join(offending)}. "
|
||||
|
||||
@@ -314,16 +314,11 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
To find the port, you can run our utility script:
|
||||
```bash
|
||||
lerobot-find-port.py
|
||||
```
|
||||
|
||||
which prints:
|
||||
|
||||
```
|
||||
Finding all available ports for the MotorsBus.
|
||||
["/dev/tty.usbmodem575E0032081", "/dev/tty.usbmodem575E0031751"]
|
||||
Remove the usb cable from your MotorsBus and press Enter when done.
|
||||
The port of this MotorsBus is /dev/tty.usbmodem575E0031751.
|
||||
Reconnect the usb cable.
|
||||
>>> Finding all available ports for the MotorsBus.
|
||||
>>> ["/dev/tty.usbmodem575E0032081", "/dev/tty.usbmodem575E0031751"]
|
||||
>>> Remove the usb cable from your MotorsBus and press Enter when done.
|
||||
>>> The port of this MotorsBus is /dev/tty.usbmodem575E0031751.
|
||||
>>> Reconnect the usb cable.
|
||||
```
|
||||
|
||||
Example of usage for 1 Feetech sts3215 motor connected to the bus:
|
||||
@@ -600,7 +595,7 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
ID, and finally programs the bus' default baud-rate.
|
||||
|
||||
Args:
|
||||
motor (str): Key of the motor in `motors`.
|
||||
motor (str): Key of the motor in :pyattr:`motors`.
|
||||
initial_baudrate (int | None, optional): Current baud-rate (skips scanning when provided).
|
||||
Defaults to None.
|
||||
initial_id (int | None, optional): Current ID (skips scanning when provided). Defaults to None.
|
||||
@@ -671,7 +666,7 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
"""Enable torque on selected motors.
|
||||
|
||||
Args:
|
||||
motors (int | str | list[str] | None, optional): Same semantics as [`~motors.motors_bus.MotorsBus.disable_torque`].
|
||||
motors (int | str | list[str] | None, optional): Same semantics as :pymeth:`disable_torque`.
|
||||
Defaults to `None`.
|
||||
num_retry (int, optional): Number of additional retry attempts on communication failure.
|
||||
Defaults to 0.
|
||||
@@ -684,12 +679,10 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
|
||||
This helper is useful to temporarily disable torque when configuring motors.
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> with bus.torque_disabled(): # doctest: +SKIP
|
||||
Examples:
|
||||
>>> with bus.torque_disabled():
|
||||
... # Safe operations here
|
||||
... pass
|
||||
```
|
||||
"""
|
||||
self.disable_torque(motors)
|
||||
try:
|
||||
@@ -702,7 +695,7 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
|
||||
Args:
|
||||
timeout_ms (int | None, optional): Timeout in *milliseconds*. If `None` (default) the method falls
|
||||
back to `default_timeout`.
|
||||
back to :pyattr:`default_timeout`.
|
||||
"""
|
||||
timeout_ms = timeout_ms if timeout_ms is not None else self.default_timeout
|
||||
self.port_handler.setPacketTimeoutMillis(timeout_ms)
|
||||
@@ -753,8 +746,8 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
|
||||
Args:
|
||||
calibration_dict (dict[str, MotorCalibration]): Calibration obtained from
|
||||
[`~motors.motors_bus.MotorsBus.read_calibration`] or crafted by the user.
|
||||
cache (bool, optional): Save the calibration to `calibration`. Defaults to True.
|
||||
:pymeth:`read_calibration` or crafted by the user.
|
||||
cache (bool, optional): Save the calibration to :pyattr:`calibration`. Defaults to True.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -762,7 +755,7 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
"""Restore factory calibration for the selected motors.
|
||||
|
||||
Homing offset is set to ``0`` and min/max position limits are set to the full usable range.
|
||||
The in-memory `calibration` is cleared.
|
||||
The in-memory :pyattr:`calibration` is cleared.
|
||||
|
||||
Args:
|
||||
motors (NameOrID | Sequence[NameOrID] | None, optional): Selection of motors. `None` (default)
|
||||
@@ -1076,9 +1069,9 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
) -> None:
|
||||
"""Write a value to a single motor's register.
|
||||
|
||||
Contrary to [`~motors.motors_bus.MotorsBus.sync_write`], this expects a response status packet emitted by the motor, which
|
||||
Contrary to :pymeth:`sync_write`, this expects a response status packet emitted by the motor, which
|
||||
provides a guarantee that the value was written to the register successfully. In consequence, it is
|
||||
slower than [`~motors.motors_bus.MotorsBus.sync_write`] but it is more reliable. It should typically be used when configuring
|
||||
slower than :pymeth:`sync_write` but it is more reliable. It should typically be used when configuring
|
||||
motors.
|
||||
|
||||
Args:
|
||||
@@ -1235,8 +1228,8 @@ class SerialMotorsBus(MotorsBusBase):
|
||||
) -> None:
|
||||
"""Write the same register on multiple motors.
|
||||
|
||||
Contrary to [`~motors.motors_bus.MotorsBus.write`], this *does not* expects a response status packet emitted by the motor, which
|
||||
can allow for lost packets. It is faster than [`~motors.motors_bus.MotorsBus.write`] and should typically be used when
|
||||
Contrary to :pymeth:`write`, this *does not* expects a response status packet emitted by the motor, which
|
||||
can allow for lost packets. It is faster than :pymeth:`write` and should typically be used when
|
||||
frequency matters and losing some packets is acceptable (e.g. teleoperation loops).
|
||||
|
||||
Args:
|
||||
|
||||
@@ -40,93 +40,44 @@ class ACTConfig(PreTrainedConfig):
|
||||
- "action" is required as an output key.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back). ACT only supports a value of 1; anything else raises in
|
||||
`__post_init__`.
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy
|
||||
is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 100):
|
||||
The size of the action prediction "chunks" in units of environment steps.
|
||||
n_action_steps (`int`, *optional*, defaults to 100):
|
||||
The number of action steps to run in the environment for one invocation of the policy. This
|
||||
should be no greater than `chunk_size`. For example, if the chunk size is 100, you may set this
|
||||
to 50: the model predicts 100 steps worth of actions, runs 50 in the environment, and throws
|
||||
the other 50 out.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to mean/std normalization for visual, state, and action features.
|
||||
vision_backbone (`str`, *optional*, defaults to `"resnet18"`):
|
||||
Name of the torchvision resnet backbone to use for encoding images.
|
||||
pretrained_backbone_weights (`str | None`, *optional*, defaults to `"ResNet18_Weights.IMAGENET1K_V1"`):
|
||||
Pretrained weights from torchvision to initialize the backbone. `None` means no pretrained
|
||||
weights.
|
||||
replace_final_stride_with_dilation (`int`, *optional*, defaults to `False`):
|
||||
Whether to replace the ResNet's final 2x2 stride with a dilated convolution.
|
||||
pre_norm (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use "pre-norm" in the transformer blocks.
|
||||
dim_model (`int`, *optional*, defaults to 512):
|
||||
The transformer blocks' main hidden dimension.
|
||||
n_heads (`int`, *optional*, defaults to 8):
|
||||
The number of heads to use in the transformer blocks' multi-head attention.
|
||||
dim_feedforward (`int`, *optional*, defaults to 3200):
|
||||
The dimension to expand the transformer's hidden dimension to in the feed-forward layers.
|
||||
feedforward_activation (`str`, *optional*, defaults to `"relu"`):
|
||||
The activation to use in the transformer block's feed-forward layers.
|
||||
n_encoder_layers (`int`, *optional*, defaults to 4):
|
||||
The number of transformer layers to use for the transformer encoder.
|
||||
n_decoder_layers (`int`, *optional*, defaults to 1):
|
||||
The number of transformer layers to use for the transformer decoder.
|
||||
use_vae (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use a variational objective during training. This introduces another transformer
|
||||
n_obs_steps: Number of environment steps worth of observations to pass to the policy (takes the
|
||||
current step and additional steps going back).
|
||||
chunk_size: The size of the action prediction "chunks" in units of environment steps.
|
||||
n_action_steps: The number of action steps to run in the environment for one invocation of the policy.
|
||||
This should be no greater than the chunk size. For example, if the chunk size size 100, you may
|
||||
set this to 50. This would mean that the model predicts 100 steps worth of actions, runs 50 in the
|
||||
environment, and throws the other 50 out.
|
||||
input_features: A dictionary defining the PolicyFeature of the input data for the policy. The key represents
|
||||
the input data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
output_features: A dictionary defining the PolicyFeature of the output data for the policy. The key represents
|
||||
the output data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
normalization_mapping: A dictionary that maps from a str value of FeatureType (e.g., "STATE", "VISUAL") to
|
||||
a corresponding NormalizationMode (e.g., NormalizationMode.MIN_MAX)
|
||||
vision_backbone: Name of the torchvision resnet backbone to use for encoding images.
|
||||
pretrained_backbone_weights: Pretrained weights from torchvision to initialize the backbone.
|
||||
`None` means no pretrained weights.
|
||||
replace_final_stride_with_dilation: Whether to replace the ResNet's final 2x2 stride with a dilated
|
||||
convolution.
|
||||
pre_norm: Whether to use "pre-norm" in the transformer blocks.
|
||||
dim_model: The transformer blocks' main hidden dimension.
|
||||
n_heads: The number of heads to use in the transformer blocks' multi-head attention.
|
||||
dim_feedforward: The dimension to expand the transformer's hidden dimension to in the feed-forward
|
||||
layers.
|
||||
feedforward_activation: The activation to use in the transformer block's feed-forward layers.
|
||||
n_encoder_layers: The number of transformer layers to use for the transformer encoder.
|
||||
n_decoder_layers: The number of transformer layers to use for the transformer decoder.
|
||||
use_vae: Whether to use a variational objective during training. This introduces another transformer
|
||||
which is used as the VAE's encoder (not to be confused with the transformer encoder - see
|
||||
documentation in the policy class).
|
||||
latent_dim (`int`, *optional*, defaults to 32):
|
||||
The VAE's latent dimension.
|
||||
n_vae_encoder_layers (`int`, *optional*, defaults to 4):
|
||||
The number of transformer layers to use for the VAE's encoder.
|
||||
temporal_ensemble_coeff (`float | None`, *optional*):
|
||||
Coefficient for the exponential weighting scheme to apply for temporal ensembling. `None` (the
|
||||
default) means temporal ensembling is not used. `n_action_steps` must be 1 when using this
|
||||
feature, as inference needs to happen at every step to form an ensemble. For more information
|
||||
on how ensembling works, see `ACTTemporalEnsembler`.
|
||||
dropout (`float`, *optional*, defaults to 0.1):
|
||||
Dropout to use in the transformer layers (see code for details).
|
||||
kl_weight (`float`, *optional*, defaults to 10.0):
|
||||
The weight to use for the KL-divergence component of the loss if the variational objective is
|
||||
enabled. Loss is then calculated as: `reconstruction_loss + kl_weight * kld_loss`.
|
||||
optimizer_lr (`float`, *optional*, defaults to 1e-05):
|
||||
Learning rate for the AdamW optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.0001):
|
||||
Weight decay for the AdamW optimizer preset.
|
||||
optimizer_lr_backbone (`float`, *optional*, defaults to 1e-05):
|
||||
Learning rate for the vision backbone's parameters in the AdamW optimizer preset.
|
||||
latent_dim: The VAE's latent dimension.
|
||||
n_vae_encoder_layers: The number of transformer layers to use for the VAE's encoder.
|
||||
temporal_ensemble_coeff: Coefficient for the exponential weighting scheme to apply for temporal
|
||||
ensembling. Defaults to None which means temporal ensembling is not used. `n_action_steps` must be
|
||||
1 when using this feature, as inference needs to happen at every step to form an ensemble. For
|
||||
more information on how ensembling works, please see `ACTTemporalEnsembler`.
|
||||
dropout: Dropout to use in the transformer layers (see code for details).
|
||||
kl_weight: The weight to use for the KL-divergence component of the loss if the variational objective
|
||||
is enabled. Loss is then calculated as: `reconstruction_loss + kl_weight * kld_loss`.
|
||||
"""
|
||||
|
||||
# Input / output structure.
|
||||
@@ -177,9 +128,9 @@ class ACTConfig(PreTrainedConfig):
|
||||
optimizer_lr_backbone: float = 1e-5
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates `vision_backbone`, `temporal_ensemble_coeff`/`n_action_steps`, `n_action_steps`/`chunk_size`, and `n_obs_steps`."""
|
||||
super().__post_init__()
|
||||
|
||||
"""Input validation (not exhaustive)."""
|
||||
if not self.vision_backbone.startswith("resnet"):
|
||||
raise ValueError(
|
||||
f"`vision_backbone` must be one of the ResNet variants. Got {self.vision_backbone}."
|
||||
@@ -200,32 +151,26 @@ class ACTConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
weight_decay=self.optimizer_weight_decay,
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return None
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if not self.image_features and not self.env_state_feature:
|
||||
raise ValueError("You must provide at least one image or the environment state among the inputs.")
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -40,7 +40,8 @@ from .configuration_act import ACTConfig
|
||||
|
||||
|
||||
class ACTPolicy(PreTrainedPolicy):
|
||||
"""Action Chunking Transformer Policy as per Learning Fine-Grained Bimanual Manipulation with Low-Cost
|
||||
"""
|
||||
Action Chunking Transformer Policy as per Learning Fine-Grained Bimanual Manipulation with Low-Cost
|
||||
Hardware (paper: https://huggingface.co/papers/2304.13705, code: https://github.com/tonyzhaozh/act)
|
||||
"""
|
||||
|
||||
@@ -54,11 +55,10 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
config: ACTConfig,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the ACT model (and, if enabled, the temporal ensembler) from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`ACTConfig`):
|
||||
Policy configuration.
|
||||
config: Policy configuration class instance or None, in which case the default instantiation of
|
||||
the configuration class is used.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -72,11 +72,6 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Splits parameters into two groups: the vision backbone, trained at `optimizer_lr_backbone`, and
|
||||
everything else, trained at the base `optimizer_lr`.
|
||||
"""
|
||||
# TODO(aliberts, rcadene): As of now, lr_backbone == lr
|
||||
# Should we remove this and just `return self.parameters()`?
|
||||
return [
|
||||
@@ -98,11 +93,7 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
]
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Resets the `ACTTemporalEnsembler` when temporal ensembling is enabled, otherwise clears the action
|
||||
queue consumed by `select_action`.
|
||||
"""
|
||||
"""This should be called whenever the environment is reset."""
|
||||
if self.config.temporal_ensemble_coeff is not None:
|
||||
self.temporal_ensembler.reset()
|
||||
else:
|
||||
@@ -110,11 +101,11 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
"""Select a single action given environment observations.
|
||||
|
||||
Returns one action at a time from a queue populated by `predict_action_chunk`, refilling it once
|
||||
it runs dry. When temporal ensembling is enabled, the queue is bypassed and the action is instead
|
||||
produced by combining chunks via `ACTTemporalEnsembler`.
|
||||
This method wraps `select_actions` in order to return one action at a time for execution in the
|
||||
environment. It works by managing the actions in a queue and only calling `select_actions` when the
|
||||
queue is empty.
|
||||
"""
|
||||
self.eval() # keeping the policy in eval mode as it could be set to train mode while queue is consumed
|
||||
|
||||
@@ -135,7 +126,7 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]."""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
self.eval()
|
||||
|
||||
if self.config.image_features:
|
||||
@@ -146,11 +137,7 @@ class ACTPolicy(PreTrainedPolicy):
|
||||
return actions
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
The loss is an L1 reconstruction loss between the predicted and target actions, plus (when
|
||||
`use_vae` is enabled) a KL-divergence term weighted by `kl_weight`.
|
||||
"""
|
||||
"""Run the batch through the model and compute the loss for training or validation."""
|
||||
if self.config.image_features:
|
||||
batch = dict(batch) # shallow copy so that adding a key doesn't modify the original
|
||||
batch[OBS_IMAGES] = [batch[key] for key in self.config.image_features]
|
||||
@@ -234,7 +221,8 @@ class ACTTemporalEnsembler:
|
||||
self.ensembled_actions_count = None
|
||||
|
||||
def update(self, actions: Tensor) -> Tensor:
|
||||
"""Takes a (batch, chunk_size, action_dim) sequence of actions, update the temporal ensemble for all
|
||||
"""
|
||||
Takes a (batch, chunk_size, action_dim) sequence of actions, update the temporal ensemble for all
|
||||
time steps, and pop/return the next batch of actions in the sequence.
|
||||
"""
|
||||
self.ensemble_weights = self.ensemble_weights.to(device=actions.device)
|
||||
@@ -638,13 +626,13 @@ class ACTDecoderLayer(nn.Module):
|
||||
decoder_pos_embed: Tensor | None = None,
|
||||
encoder_pos_embed: Tensor | None = None,
|
||||
) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: (Decoder Sequence, Batch, Channel) tensor of input tokens.
|
||||
encoder_out: (Encoder Sequence, B, C) output features from the last layer of the encoder we are
|
||||
cross-attending with.
|
||||
encoder_pos_embed: (ES, 1, C) positional embedding for keys (from the encoder).
|
||||
decoder_pos_embed: (DS, 1, C) positional embedding for the queries (from the decoder).
|
||||
|
||||
Returns:
|
||||
(DS, B, C) tensor of decoder output features.
|
||||
"""
|
||||
@@ -683,11 +671,9 @@ def create_sinusoidal_pos_embedding(num_positions: int, dimension: int) -> Tenso
|
||||
"""1D sinusoidal positional embeddings as in Attention is All You Need.
|
||||
|
||||
Args:
|
||||
num_positions (`int`): Number of positions to embed (the sequence length).
|
||||
dimension (`int`): The embedding dimension.
|
||||
num_positions: Number of token positions required.
|
||||
Returns: (num_positions, dimension) position embeddings (the first dimension is the batch dimension).
|
||||
|
||||
Returns:
|
||||
`(num_positions, dimension)` position embeddings (the first dimension is the batch dimension).
|
||||
"""
|
||||
|
||||
def get_position_angle_vec(position):
|
||||
@@ -707,8 +693,9 @@ class ACTSinusoidalPositionEmbedding2d(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(self, dimension: int):
|
||||
"""Args:
|
||||
dimension: The desired dimension of the embeddings.
|
||||
"""
|
||||
Args:
|
||||
dimension: The desired dimension of the embeddings.
|
||||
"""
|
||||
super().__init__()
|
||||
self.dimension = dimension
|
||||
@@ -718,9 +705,9 @@ class ACTSinusoidalPositionEmbedding2d(nn.Module):
|
||||
self._temperature = 10000
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: A (B, C, H, W) batch of 2D feature map to generate the embeddings for.
|
||||
|
||||
Returns:
|
||||
A (1, C, H, W) batch of corresponding sinusoidal positional embeddings.
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,7 @@ def make_act_pre_post_processors(
|
||||
|
||||
Args:
|
||||
config (ACTConfig): The ACT policy configuration object.
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None, *optional*): A dictionary containing dataset
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None): A dictionary containing dataset
|
||||
statistics (e.g., mean and std) used for normalization. Defaults to None.
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -41,135 +41,63 @@ class DiffusionConfig(PreTrainedConfig):
|
||||
- "action" is required as an output key.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 2):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy
|
||||
is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
horizon (`int`, *optional*, defaults to 64):
|
||||
Diffusion model action prediction size as detailed in `DiffusionPolicy.select_action`.
|
||||
n_action_steps (`int`, *optional*, defaults to 32):
|
||||
The number of action steps to run in the environment for one invocation of the policy. See
|
||||
`DiffusionPolicy.select_action` for more details.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to mean/std normalization for visual features and min/max normalization for
|
||||
state and action features.
|
||||
drop_n_last_frames (`int`, *optional*, defaults to 7):
|
||||
Number of frames dropped from the end of each episode when sampling training windows, which
|
||||
avoids excessive padding. Should track `horizon - n_action_steps - n_obs_steps + 1`.
|
||||
vision_backbone (`str`, *optional*, defaults to `"resnet18"`):
|
||||
Name of the torchvision resnet backbone to use for encoding images.
|
||||
resize_shape (`tuple[int, int] | None`, *optional*):
|
||||
(H, W) shape to resize images to as a preprocessing step for the vision backbone. `None`
|
||||
disables resizing, so the original image resolution is used.
|
||||
crop_ratio (`float`, *optional*, defaults to 1.0):
|
||||
Ratio in (0, 1] used to derive the crop size from `resize_shape` (`crop_h =
|
||||
int(resize_shape[0] * crop_ratio)`, likewise for width). Set to 1.0 to disable cropping. Only
|
||||
takes effect when `resize_shape` is not `None`.
|
||||
crop_shape (`tuple[int, int] | None`, *optional*):
|
||||
(H, W) shape to crop images to. Computed automatically when `resize_shape` is set and
|
||||
`crop_ratio` < 1.0. Can also be set directly for legacy configs that use crop-only (without
|
||||
resize). `None`, with no derivation applying, means no cropping.
|
||||
crop_is_random (`bool`, *optional*, defaults to `True`):
|
||||
Whether the crop should be random at training time (it's always a center crop in eval mode).
|
||||
pretrained_backbone_weights (`str | None`, *optional*, defaults to `"ResNet18_Weights.IMAGENET1K_V1"`):
|
||||
Pretrained weights from torchvision to initialize the backbone. `None` means no pretrained
|
||||
weights.
|
||||
use_group_norm (`bool`, *optional*, defaults to `False`):
|
||||
Whether to replace batch normalization with group normalization in the backbone. The group
|
||||
sizes are set to be about 16 (`feature_dim // 16`).
|
||||
spatial_softmax_num_keypoints (`int`, *optional*, defaults to 32):
|
||||
Number of keypoints for SpatialSoftmax.
|
||||
use_separate_rgb_encoder_per_camera (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use a separate RGB encoder for each camera view.
|
||||
down_dims (`tuple[int, ...]`, *optional*, defaults to `(512, 1024, 2048)`):
|
||||
Feature dimension for each stage of temporal downsampling in the diffusion modeling Unet. You
|
||||
may provide a variable number of dimensions, therefore also controlling the degree of
|
||||
n_obs_steps: Number of environment steps worth of observations to pass to the policy (takes the
|
||||
current step and additional steps going back).
|
||||
horizon: Diffusion model action prediction size as detailed in `DiffusionPolicy.select_action`.
|
||||
n_action_steps: The number of action steps to run in the environment for one invocation of the policy.
|
||||
See `DiffusionPolicy.select_action` for more details.
|
||||
input_features: A dictionary defining the PolicyFeature of the input data for the policy. The key represents
|
||||
the input data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
output_features: A dictionary defining the PolicyFeature of the output data for the policy. The key represents
|
||||
the output data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
normalization_mapping: A dictionary that maps from a str value of FeatureType (e.g., "STATE", "VISUAL") to
|
||||
a corresponding NormalizationMode (e.g., NormalizationMode.MIN_MAX)
|
||||
vision_backbone: Name of the torchvision resnet backbone to use for encoding images.
|
||||
resize_shape: (H, W) shape to resize images to as a preprocessing step for the vision
|
||||
backbone. If None, no resizing is done and the original image resolution is used.
|
||||
crop_ratio: Ratio in (0, 1] used to derive the crop size from resize_shape
|
||||
(crop_h = int(resize_shape[0] * crop_ratio), likewise for width).
|
||||
Set to 1.0 to disable cropping. Only takes effect when resize_shape is not None.
|
||||
crop_shape: (H, W) shape to crop images to. When resize_shape is set and crop_ratio < 1.0,
|
||||
this is computed automatically. Can also be set directly for legacy configs that use
|
||||
crop-only (without resize). If None and no derivation applies, no cropping is done.
|
||||
crop_is_random: Whether the crop should be random at training time (it's always a center
|
||||
crop in eval mode).
|
||||
pretrained_backbone_weights: Pretrained weights from torchvision to initialize the backbone.
|
||||
`None` means no pretrained weights.
|
||||
use_group_norm: Whether to replace batch normalization with group normalization in the backbone.
|
||||
The group sizes are set to be about 16 (to be precise, feature_dim // 16).
|
||||
spatial_softmax_num_keypoints: Number of keypoints for SpatialSoftmax.
|
||||
use_separate_rgb_encoder_per_camera: Whether to use a separate RGB encoder for each camera view.
|
||||
down_dims: Feature dimension for each stage of temporal downsampling in the diffusion modeling Unet.
|
||||
You may provide a variable number of dimensions, therefore also controlling the degree of
|
||||
downsampling.
|
||||
kernel_size (`int`, *optional*, defaults to 5):
|
||||
The convolutional kernel size of the diffusion modeling Unet.
|
||||
n_groups (`int`, *optional*, defaults to 8):
|
||||
Number of groups used in the group norm of the Unet's convolutional blocks.
|
||||
diffusion_step_embed_dim (`int`, *optional*, defaults to 128):
|
||||
The Unet is conditioned on the diffusion timestep via a small non-linear network. This is the
|
||||
output dimension of that network, i.e. the embedding dimension.
|
||||
use_film_scale_modulation (`bool`, *optional*, defaults to `True`):
|
||||
FiLM (https://huggingface.co/papers/1709.07871) is used for the Unet conditioning. Bias
|
||||
modulation is used by default, while this parameter indicates whether to also use scale
|
||||
kernel_size: The convolutional kernel size of the diffusion modeling Unet.
|
||||
n_groups: Number of groups used in the group norm of the Unet's convolutional blocks.
|
||||
diffusion_step_embed_dim: The Unet is conditioned on the diffusion timestep via a small non-linear
|
||||
network. This is the output dimension of that network, i.e., the embedding dimension.
|
||||
use_film_scale_modulation: FiLM (https://huggingface.co/papers/1709.07871) is used for the Unet conditioning.
|
||||
Bias modulation is used be default, while this parameter indicates whether to also use scale
|
||||
modulation.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to checkpoint the Unet residual blocks during training. This reduces activation memory
|
||||
at the cost of recomputing those blocks during the backward pass.
|
||||
noise_scheduler_type (`str`, *optional*, defaults to `"DDPM"`):
|
||||
Name of the noise scheduler to use. Supported options: `"DDPM"`, `"DDIM"`.
|
||||
num_train_timesteps (`int`, *optional*, defaults to 100):
|
||||
Number of diffusion steps for the forward diffusion schedule.
|
||||
beta_schedule (`str`, *optional*, defaults to `"squaredcos_cap_v2"`):
|
||||
Name of the diffusion beta schedule as per `DDPMScheduler` from Hugging Face diffusers.
|
||||
beta_start (`float`, *optional*, defaults to 0.0001):
|
||||
Beta value for the first forward-diffusion step.
|
||||
beta_end (`float`, *optional*, defaults to 0.02):
|
||||
Beta value for the last forward-diffusion step.
|
||||
prediction_type (`str`, *optional*, defaults to `"epsilon"`):
|
||||
The type of prediction that the diffusion modeling Unet makes. Choose from `"epsilon"` or
|
||||
`"sample"`. These have equivalent outcomes from a latent variable modeling perspective, but
|
||||
`"epsilon"` has been shown to work better in many deep neural network settings.
|
||||
clip_sample (`bool`, *optional*, defaults to `True`):
|
||||
Whether to clip the sample to `[-clip_sample_range, +clip_sample_range]` for each denoising
|
||||
step at inference time. This requires the action space to be normalized to fit within that
|
||||
range.
|
||||
clip_sample_range (`float`, *optional*, defaults to 1.0):
|
||||
The magnitude of the clipping range described above.
|
||||
num_inference_steps (`int | None`, *optional*):
|
||||
Number of reverse diffusion steps to use at inference time (steps are evenly spaced). If not
|
||||
provided, defaults to the same value as `num_train_timesteps`.
|
||||
compile_model (`bool`, *optional*, defaults to `False`):
|
||||
Whether to compile the Unet with `torch.compile`.
|
||||
compile_mode (`str`, *optional*, defaults to `"reduce-overhead"`):
|
||||
`torch.compile` mode to use when `compile_model` is enabled.
|
||||
do_mask_loss_for_padding (`bool`, *optional*, defaults to `False`):
|
||||
Whether to mask the loss when there are copy-padded actions. See `LeRobotDataset` and
|
||||
`load_previous_and_future_frames` for more information. This defaults to `False` as the
|
||||
original Diffusion Policy implementation does the same.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Learning rate for the Adam optimizer preset.
|
||||
optimizer_betas (`tuple`, *optional*, defaults to `(0.95, 0.999)`):
|
||||
Adam optimizer's beta coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Adam optimizer's epsilon for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-06):
|
||||
Weight decay for the Adam optimizer preset.
|
||||
scheduler_name (`str`, *optional*, defaults to `"cosine"`):
|
||||
Name of the LR scheduler preset to use.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 500):
|
||||
Number of warmup steps for the LR scheduler preset.
|
||||
gradient_checkpointing: Whether to checkpoint the Unet residual blocks during training. This reduces
|
||||
activation memory at the cost of recomputing those blocks during the backward pass.
|
||||
noise_scheduler_type: Name of the noise scheduler to use. Supported options: ["DDPM", "DDIM"].
|
||||
num_train_timesteps: Number of diffusion steps for the forward diffusion schedule.
|
||||
beta_schedule: Name of the diffusion beta schedule as per DDPMScheduler from Hugging Face diffusers.
|
||||
beta_start: Beta value for the first forward-diffusion step.
|
||||
beta_end: Beta value for the last forward-diffusion step.
|
||||
prediction_type: The type of prediction that the diffusion modeling Unet makes. Choose from "epsilon"
|
||||
or "sample". These have equivalent outcomes from a latent variable modeling perspective, but
|
||||
"epsilon" has been shown to work better in many deep neural network settings.
|
||||
clip_sample: Whether to clip the sample to [-`clip_sample_range`, +`clip_sample_range`] for each
|
||||
denoising step at inference time. WARNING: you will need to make sure your action-space is
|
||||
normalized to fit within this range.
|
||||
clip_sample_range: The magnitude of the clipping range as described above.
|
||||
num_inference_steps: Number of reverse diffusion steps to use at inference time (steps are evenly
|
||||
spaced). If not provided, this defaults to be the same as `num_train_timesteps`.
|
||||
do_mask_loss_for_padding: Whether to mask the loss when there are copy-padded actions. See
|
||||
`LeRobotDataset` and `load_previous_and_future_frames` for more information. Note, this defaults
|
||||
to False as the original Diffusion Policy implementation does the same.
|
||||
"""
|
||||
|
||||
# Inputs / output structure.
|
||||
@@ -236,9 +164,9 @@ class DiffusionConfig(PreTrainedConfig):
|
||||
scheduler_warmup_steps: int = 500
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates image/state feature presence and normalization-mode compatibility with the configured vision backbone."""
|
||||
super().__post_init__()
|
||||
|
||||
"""Input validation (not exhaustive)."""
|
||||
if not self.vision_backbone.startswith("resnet"):
|
||||
raise ValueError(
|
||||
f"`vision_backbone` must be one of the ResNet variants. Got {self.vision_backbone}."
|
||||
@@ -285,7 +213,6 @@ class DiffusionConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -294,14 +221,12 @@ class DiffusionConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> DiffuserSchedulerConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return DiffuserSchedulerConfig(
|
||||
name=self.scheduler_name,
|
||||
num_warmup_steps=self.scheduler_warmup_steps,
|
||||
)
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if len(self.image_features) == 0 and self.env_state_feature is None:
|
||||
raise ValueError("You must provide at least one image or the environment state among the inputs.")
|
||||
|
||||
@@ -324,15 +249,12 @@ class DiffusionConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, 1))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, 1 - self.n_obs_steps + self.horizon))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -54,7 +54,8 @@ from .configuration_diffusion import DiffusionConfig
|
||||
|
||||
|
||||
class DiffusionPolicy(PreTrainedPolicy):
|
||||
"""Diffusion Policy as per "Diffusion Policy: Visuomotor Policy Learning via Action Diffusion"
|
||||
"""
|
||||
Diffusion Policy as per "Diffusion Policy: Visuomotor Policy Learning via Action Diffusion"
|
||||
(paper: https://huggingface.co/papers/2303.04137, code: https://github.com/real-stanford/diffusion_policy).
|
||||
"""
|
||||
|
||||
@@ -66,11 +67,12 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
config: DiffusionConfig,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the diffusion model from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`DiffusionConfig`):
|
||||
Policy configuration.
|
||||
config: Policy configuration class instance or None, in which case the default instantiation of
|
||||
the configuration class is used.
|
||||
dataset_stats: Dataset statistics to be used for normalization. If not passed here, it is expected
|
||||
that they will be passed with a call to `load_state_dict` before the policy is used.
|
||||
"""
|
||||
require_package("diffusers", extra="diffusion")
|
||||
super().__init__(config)
|
||||
@@ -85,14 +87,10 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.diffusion.parameters()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Clears the observation and action queues consumed by `select_action`.
|
||||
"""
|
||||
"""Clear observation and action queues. Should be called on `env.reset()`"""
|
||||
self._queues = {
|
||||
OBS_STATE: deque(maxlen=self.config.n_obs_steps),
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
@@ -104,7 +102,7 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
"""Predict a chunk of actions given environment observations.
|
||||
|
||||
Supports two modes:
|
||||
- Online (queues populated via select_action): stacks observations from internal queues.
|
||||
@@ -125,7 +123,7 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
"""Select a single action given environment observations.
|
||||
|
||||
This method handles caching a history of observations and an action trajectory generated by the
|
||||
underlying diffusion model. Here's how it works:
|
||||
@@ -163,7 +161,7 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
return action
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, None]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`]."""
|
||||
"""Run the batch through the model and compute the loss for training or validation."""
|
||||
if self.config.image_features:
|
||||
batch = dict(batch) # shallow copy so that adding a key doesn't modify the original
|
||||
for key in self.config.image_features:
|
||||
@@ -176,7 +174,8 @@ class DiffusionPolicy(PreTrainedPolicy):
|
||||
|
||||
|
||||
def _make_noise_scheduler(name: str, **kwargs: dict):
|
||||
"""Factory for noise scheduler instances of the requested type. All kwargs are passed
|
||||
"""
|
||||
Factory for noise scheduler instances of the requested type. All kwargs are passed
|
||||
to the scheduler.
|
||||
"""
|
||||
require_package("diffusers", extra="diffusion")
|
||||
@@ -307,7 +306,8 @@ class DiffusionModel(nn.Module):
|
||||
return torch.cat(global_cond_feats, dim=-1).flatten(start_dim=1)
|
||||
|
||||
def generate_actions(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor:
|
||||
"""This function expects `batch` to have:
|
||||
"""
|
||||
This function expects `batch` to have:
|
||||
{
|
||||
"observation.state": (B, n_obs_steps, state_dim)
|
||||
|
||||
@@ -333,7 +333,8 @@ class DiffusionModel(nn.Module):
|
||||
return actions
|
||||
|
||||
def compute_loss(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""This function expects `batch` to have (at least):
|
||||
"""
|
||||
This function expects `batch` to have (at least):
|
||||
{
|
||||
"observation.state": (B, n_obs_steps, state_dim)
|
||||
|
||||
@@ -400,7 +401,8 @@ class DiffusionModel(nn.Module):
|
||||
|
||||
|
||||
class SpatialSoftmax(nn.Module):
|
||||
"""Spatial Soft Argmax operation described in "Deep Spatial Autoencoders for Visuomotor Learning" by Finn et al.
|
||||
"""
|
||||
Spatial Soft Argmax operation described in "Deep Spatial Autoencoders for Visuomotor Learning" by Finn et al.
|
||||
(https://huggingface.co/papers/1509.06113). A minimal port of the robomimic implementation.
|
||||
|
||||
At a high level, this takes 2D feature maps (from a convnet/ViT) and returns the "center of mass"
|
||||
@@ -422,9 +424,10 @@ class SpatialSoftmax(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(self, input_shape, num_kp=None):
|
||||
"""Args:
|
||||
input_shape (list): (C, H, W) input feature map shape.
|
||||
num_kp (int): number of keypoints in output. If None, output will have the same number of channels as input.
|
||||
"""
|
||||
Args:
|
||||
input_shape (list): (C, H, W) input feature map shape.
|
||||
num_kp (int): number of keypoints in output. If None, output will have the same number of channels as input.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
@@ -447,9 +450,9 @@ class SpatialSoftmax(nn.Module):
|
||||
self.register_buffer("pos_grid", torch.cat([pos_x, pos_y], dim=1))
|
||||
|
||||
def forward(self, features: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
features: (B, C, H, W) input feature maps.
|
||||
|
||||
Returns:
|
||||
(B, K, 2) image-space coordinates of keypoints.
|
||||
"""
|
||||
@@ -533,9 +536,9 @@ class DiffusionRgbEncoder(nn.Module):
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: (B, C, H, W) image tensor with pixel values in [0, 1].
|
||||
|
||||
Returns:
|
||||
(B, D) image feature.
|
||||
"""
|
||||
@@ -559,11 +562,11 @@ class DiffusionRgbEncoder(nn.Module):
|
||||
def _replace_submodules(
|
||||
root_module: nn.Module, predicate: Callable[[nn.Module], bool], func: Callable[[nn.Module], nn.Module]
|
||||
) -> nn.Module:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
root_module: The module for which the submodules need to be replaced
|
||||
predicate: Takes a module as an argument and must return True if the that module is to be replaced.
|
||||
func: Takes a module as an argument and returns a new module to replace it with.
|
||||
|
||||
Returns:
|
||||
The root module with its submodules replaced.
|
||||
"""
|
||||
@@ -705,12 +708,12 @@ class DiffusionConditionalUnet1d(nn.Module):
|
||||
)
|
||||
|
||||
def forward(self, x: Tensor, timestep: Tensor | int, global_cond=None) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: (B, T, input_dim) tensor for input to the Unet.
|
||||
timestep: (B,) tensor of (timestep_we_are_denoising_from - 1).
|
||||
global_cond: (B, global_cond_dim)
|
||||
output: (B, T, input_dim)
|
||||
|
||||
Returns:
|
||||
(B, T, input_dim) diffusion model prediction.
|
||||
"""
|
||||
@@ -795,10 +798,10 @@ class DiffusionConditionalResidualBlock1d(nn.Module):
|
||||
)
|
||||
|
||||
def forward(self, x: Tensor, cond: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: (B, in_channels, T)
|
||||
cond: (B, cond_dim)
|
||||
|
||||
Returns:
|
||||
(B, out_channels, T)
|
||||
"""
|
||||
|
||||
@@ -34,7 +34,8 @@ def make_diffusion_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for a diffusion policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for a diffusion policy.
|
||||
|
||||
The pre-processing pipeline prepares the input data for the model by:
|
||||
1. Renaming features.
|
||||
@@ -47,8 +48,10 @@ def make_diffusion_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`DiffusionConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the diffusion policy,
|
||||
containing feature definitions, normalization mappings, and device information.
|
||||
dataset_stats: A dictionary of statistics used for normalization.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
|
||||
@@ -42,117 +42,7 @@ else:
|
||||
@PreTrainedConfig.register_subclass("eo1")
|
||||
@dataclass
|
||||
class EO1Config(PreTrainedConfig):
|
||||
"""Configuration for native EO1 policy integration in LeRobot.
|
||||
|
||||
EO1 wraps a Qwen2.5-VL vision-language backbone with a flow-matching action head: the backbone attends
|
||||
over interleaved vision/language/state/action tokens, and the head denoises an action chunk from noise
|
||||
via Euler integration.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy.
|
||||
input_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Input feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
output_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Output feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
device (`str`, *optional*):
|
||||
Torch device to run the policy on, e.g. `"cuda"` or `"cpu"`. Auto-selected when unset or
|
||||
unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str`, *optional*):
|
||||
Hub repository id to push the policy to.
|
||||
private (`bool`, *optional*):
|
||||
Whether the pushed Hub repository is private.
|
||||
tags (`list[str]`, *optional*):
|
||||
Tags to attach to the policy on the Hub.
|
||||
license (`str`, *optional*):
|
||||
License identifier for the policy on the Hub.
|
||||
pretrained_path (`Path`, *optional*):
|
||||
Repo id or local directory of pretrained weights saved with `save_pretrained`. Left unset to
|
||||
initialize from scratch.
|
||||
pretrained_revision (`str`, *optional*):
|
||||
Hub revision to pin when loading `pretrained_path`.
|
||||
vlm_base (`str`, *optional*, defaults to `"Qwen/Qwen2.5-VL-3B-Instruct"`):
|
||||
Hugging Face Hub id of the Qwen2.5-VL backbone used to initialize the vision-language model.
|
||||
vlm_config (`dict`, *optional*):
|
||||
Serialized Qwen2.5-VL backbone config. Populated automatically from `vlm_base` in
|
||||
`__post_init__` when left unset.
|
||||
image_min_pixels (`int`, *optional*, defaults to 50176):
|
||||
Minimum number of pixels the vision processor resizes an image down to.
|
||||
image_max_pixels (`int`, *optional*, defaults to 100352):
|
||||
Maximum number of pixels the vision processor resizes an image up to.
|
||||
use_fast_processor (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use the Hugging Face "fast" image processor.
|
||||
chunk_size (`int`, *optional*, defaults to 8):
|
||||
Number of actions predicted per flow-matching sampling call.
|
||||
n_action_steps (`int`, *optional*, defaults to 8):
|
||||
Number of actions from a predicted chunk that are actually executed before re-querying the
|
||||
policy. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Padded dimensionality of the state vector fed to the flow-matching head.
|
||||
max_action_dim (`int`, *optional*, defaults to 32):
|
||||
Padded dimensionality of the action vector fed to the flow-matching head.
|
||||
num_denoise_steps (`int`, *optional*, defaults to 10):
|
||||
Number of Euler integration steps used to sample an action chunk.
|
||||
num_action_layers (`int`, *optional*, defaults to 2):
|
||||
Number of linear layers in the action output projector MLP.
|
||||
action_act (`str`, *optional*, defaults to `"linear"`):
|
||||
Activation used between the action output projector's layers.
|
||||
time_sampling_beta_alpha (`float`, *optional*, defaults to 1.5):
|
||||
Alpha parameter of the Beta distribution used to sample the flow-matching timestep during
|
||||
training.
|
||||
time_sampling_beta_beta (`float`, *optional*, defaults to 1.0):
|
||||
Beta parameter of the same Beta distribution.
|
||||
time_sampling_scale (`float`, *optional*, defaults to 0.999):
|
||||
Scale applied to the sampled Beta timestep.
|
||||
time_sampling_offset (`float`, *optional*, defaults to 0.001):
|
||||
Offset added to the scaled Beta timestep.
|
||||
min_period (`float`, *optional*, defaults to 0.004):
|
||||
Minimum period of the sinusoidal timestep embedding.
|
||||
max_period (`float`, *optional*, defaults to 4.0):
|
||||
Maximum period of the sinusoidal timestep embedding.
|
||||
supervise_padding_action_dims (`bool`, *optional*, defaults to `True`):
|
||||
Whether the flow-matching loss also supervises the padded action dimensions that lie beyond
|
||||
the dataset's real action size.
|
||||
supervise_padding_actions (`bool`, *optional*, defaults to `True`):
|
||||
Whether the flow-matching loss also supervises padded action timesteps. Padded timesteps are
|
||||
marked by `action_is_pad`.
|
||||
dtype (`str`, *optional*, defaults to `"auto"`):
|
||||
Dtype requested for the Qwen backbone. `"auto"` follows the backbone checkpoint's default
|
||||
dtype (bf16 for Qwen2.5-VL); the flow-matching head always keeps its own parameters in fp32
|
||||
regardless. Other supported values are `"bfloat16"` and `"float32"`.
|
||||
force_fp32_autocast (`bool`, *optional*, defaults to `True`):
|
||||
Whether to disable autocast around the flow-matching head so its projections run in fp32 even
|
||||
when the backbone runs under bf16 autocast.
|
||||
attn_implementation (`str`, *optional*):
|
||||
Attention backend requested for the Qwen backbone, e.g. `"sdpa"` or `"flash_attention_2"`.
|
||||
Left unset to use the backbone's default.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to enable gradient checkpointing on the Qwen backbone to reduce memory usage.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps each `FeatureType` to the `NormalizationMode` used to normalize/unnormalize it.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Peak learning rate used to build the default `AdamWConfig` optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.999)`):
|
||||
Adam beta coefficients for the default optimizer preset.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Adam epsilon for the default optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.1):
|
||||
Weight decay for the default optimizer preset.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0):
|
||||
Gradient-norm clipping threshold for the default optimizer preset.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 900):
|
||||
Number of warmup steps for the default cosine-decay-with-warmup scheduler preset.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the default scheduler preset.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 0.0):
|
||||
Learning rate reached at the end of the default scheduler's decay.
|
||||
"""
|
||||
"""Configuration for native EO1 policy integration in LeRobot."""
|
||||
|
||||
vlm_base: str = "Qwen/Qwen2.5-VL-3B-Instruct"
|
||||
vlm_config: dict | None = None
|
||||
@@ -222,7 +112,6 @@ class EO1Config(PreTrainedConfig):
|
||||
scheduler_decay_lr: float = 0.0
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the VLM backbone/tokenizer configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
if self.n_action_steps > self.chunk_size:
|
||||
@@ -237,7 +126,6 @@ class EO1Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def vlm_backbone_config(self) -> Qwen2_5_VLConfig:
|
||||
"""Build the Qwen2.5-VL backbone config from `vlm_config`, applying `attn_implementation` if set."""
|
||||
require_package("transformers", extra="eo1")
|
||||
config_dict = deepcopy(self.vlm_config)
|
||||
if self.attn_implementation is not None:
|
||||
@@ -246,12 +134,10 @@ class EO1Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def text_config(self) -> Qwen2_5_VLTextConfig:
|
||||
"""The text-tower sub-config of `vlm_backbone_config`."""
|
||||
return self.vlm_backbone_config.text_config
|
||||
|
||||
@property
|
||||
def vision_config(self) -> Qwen2_5_VLVisionConfig:
|
||||
"""The vision-tower sub-config of `vlm_backbone_config`."""
|
||||
return self.vlm_backbone_config.vision_config
|
||||
|
||||
def validate_features(self) -> None:
|
||||
@@ -278,7 +164,6 @@ class EO1Config(PreTrainedConfig):
|
||||
self.output_features[ACTION] = action_feature
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -288,7 +173,6 @@ class EO1Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -298,15 +182,12 @@ class EO1Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -54,14 +54,6 @@ class EO1Policy(PreTrainedPolicy):
|
||||
name = "eo1"
|
||||
|
||||
def __init__(self, config: EO1Config, **kwargs):
|
||||
"""Build the Qwen2.5-VL backbone and the flow-matching action head.
|
||||
|
||||
Args:
|
||||
config (`EO1Config`):
|
||||
Policy configuration. Also drives whether the Qwen backbone is loaded from
|
||||
`config.vlm_base` (fresh initialization) or reconstructed from `config.vlm_backbone_config`
|
||||
(resuming from `config.pretrained_path`).
|
||||
"""
|
||||
require_package("transformers", extra="eo1")
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -88,7 +80,6 @@ class EO1Policy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the action queue used by `select_action`."""
|
||||
self._action_queue = deque(maxlen=self.config.n_action_steps)
|
||||
|
||||
@staticmethod
|
||||
@@ -96,11 +87,6 @@ class EO1Policy(PreTrainedPolicy):
|
||||
return {key: value for key, value in batch.items() if key not in excluded_keys}
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Computes the flow-matching loss: the mean squared error between the noise-minus-action target and
|
||||
the velocity predicted by the Qwen backbone plus flow head at a sampled timestep.
|
||||
"""
|
||||
state = self.prepare_state(batch[OBS_STATE])
|
||||
actions = self.prepare_action(batch[ACTION])
|
||||
model_inputs = self._get_model_inputs(batch, {OBS_STATE, ACTION})
|
||||
@@ -111,11 +97,6 @@ class EO1Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Samples the chunk by Euler-integrating the flow-matching head from noise, then slices it back down
|
||||
to the dataset's real action dimensionality (undoing the `max_action_dim` padding).
|
||||
"""
|
||||
self.eval()
|
||||
|
||||
states = self.prepare_state(batch[OBS_STATE])
|
||||
@@ -126,16 +107,13 @@ class EO1Policy(PreTrainedPolicy):
|
||||
return actions[:, :, :original_action_dim]
|
||||
|
||||
def prepare_state(self, state: Tensor) -> Tensor:
|
||||
"""Zero-pad a state tensor up to `config.max_state_dim` for the flow-matching head."""
|
||||
return pad_vector(state, self.config.max_state_dim)
|
||||
|
||||
def prepare_action(self, action: Tensor) -> Tensor:
|
||||
"""Zero-pad an action tensor up to `config.max_action_dim` for the flow-matching head."""
|
||||
return pad_vector(action, self.config.max_action_dim)
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Uses an action queue populated by `predict_action_chunk`."""
|
||||
self.eval()
|
||||
|
||||
if len(self._action_queue) == 0:
|
||||
@@ -145,7 +123,6 @@ class EO1Policy(PreTrainedPolicy):
|
||||
return self._action_queue.popleft()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]. Trains every policy parameter with a single learning rate."""
|
||||
return self.parameters()
|
||||
|
||||
|
||||
@@ -381,6 +358,7 @@ class EO1VisionFlowMatchingModel(nn.Module):
|
||||
**kwargs,
|
||||
) -> Tensor:
|
||||
"""Run the EO1 training forward pass and compute the flow-matching loss."""
|
||||
|
||||
# 1. Build the EO1 prefix with state placeholders resolved.
|
||||
inputs_embeds = self.embed_prefix(
|
||||
input_ids,
|
||||
|
||||
@@ -31,155 +31,6 @@ logger = logging.getLogger(__name__)
|
||||
@PreTrainedConfig.register_subclass("evo1")
|
||||
@dataclass
|
||||
class Evo1Config(PreTrainedConfig):
|
||||
"""Configuration for the EVO1 vision-language-action policy.
|
||||
|
||||
EVO1 pairs an InternVL3 vision-language backbone with a flow-matching action head. Training proceeds
|
||||
in two stages (`training_stage`): stage 1 freezes the VLM and trains only the action head, stage 2
|
||||
fine-tunes the whole model.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy.
|
||||
input_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Input feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
output_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Output feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
device (`str`, *optional*):
|
||||
Torch device to run the policy on, e.g. `"cuda"` or `"cpu"`. Auto-selected when unset or
|
||||
unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use Automatic Mixed Precision. EVO1 also manages its own bfloat16 autocast around
|
||||
its forward passes independently of this flag; see `dtype`-related fields below.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str`, *optional*):
|
||||
Hub repository id to push the policy to.
|
||||
private (`bool`, *optional*):
|
||||
Whether the pushed Hub repository is private.
|
||||
tags (`list[str]`, *optional*):
|
||||
Tags to attach to the policy on the Hub.
|
||||
license (`str`, *optional*):
|
||||
License identifier for the policy on the Hub.
|
||||
pretrained_path (`Path`, *optional*):
|
||||
Repo id or local directory of pretrained weights saved with `save_pretrained`. Left unset to
|
||||
initialize from scratch.
|
||||
pretrained_revision (`str`, *optional*):
|
||||
Hub revision to pin when loading `pretrained_path`.
|
||||
training_stage (`str`, *optional*, defaults to `"stage1"`):
|
||||
Either `"stage1"` (VLM frozen, only the action head trains) or `"stage2"` (the whole model
|
||||
trains). Drives the default `finetune_*` flags unless they are set explicitly and
|
||||
`apply_training_stage_defaults` is `False`.
|
||||
chunk_size (`int`, *optional*, defaults to 50):
|
||||
Number of actions predicted by the flow-matching head per inference call.
|
||||
n_action_steps (`int`, *optional*, defaults to 50):
|
||||
Number of actions from a predicted chunk that are actually executed before re-querying the
|
||||
policy. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 24):
|
||||
Padded dimensionality of the state vector fed to the action head.
|
||||
max_action_dim (`int`, *optional*, defaults to 24):
|
||||
Padded dimensionality of the action vector fed to the action head.
|
||||
max_views (`int`, *optional*, defaults to 3):
|
||||
Maximum number of camera streams the policy accepts.
|
||||
image_resolution (`tuple[int, int]`, *optional*, defaults to `(448, 448)`):
|
||||
Target resolution images are resized to before the InternVL3 embedder. Must be square.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of placeholder, always-masked-out camera views added to `input_features` so the batch
|
||||
has a fixed number of views regardless of how many real cameras the dataset provides.
|
||||
postprocess_action_dim (`int`, *optional*):
|
||||
Overrides the action dimensionality the postprocessor crops predictions down to. Falls back to
|
||||
the dataset's action feature width, or `max_action_dim` if that is unavailable.
|
||||
binarize_gripper (`bool`, *optional*, defaults to `False`):
|
||||
Whether the postprocessor snaps the gripper action channel to one of two fixed values instead
|
||||
of passing through the continuous prediction.
|
||||
gripper_index (`int`, *optional*, defaults to 6):
|
||||
Index of the gripper channel within the action vector, used when `binarize_gripper` is `True`.
|
||||
gripper_threshold (`float`, *optional*, defaults to 0.5):
|
||||
Decision threshold applied to the gripper channel when `binarize_gripper` is `True`.
|
||||
gripper_below_threshold_value (`float`, *optional*, defaults to 1.0):
|
||||
Value written to the gripper channel when it is at or below `gripper_threshold`.
|
||||
gripper_above_threshold_value (`float`, *optional*, defaults to -1.0):
|
||||
Value written to the gripper channel when it is above `gripper_threshold`.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps each `FeatureType` to the `NormalizationMode` used to normalize/unnormalize it.
|
||||
vlm_model_name (`str`, *optional*, defaults to `"OpenGVLab/InternVL3-1B-hf"`):
|
||||
Hugging Face Hub id of the InternVL3 vision-language backbone.
|
||||
vlm_num_layers (`int`, *optional*, defaults to 14):
|
||||
Number of transformer layers kept from the InternVL3 language model. `None` keeps all of them.
|
||||
vlm_dtype (`str`, *optional*, defaults to `"bfloat16"`):
|
||||
Dtype the InternVL3 backbone is loaded in.
|
||||
max_text_length (`int`, *optional*, defaults to 1024):
|
||||
Maximum token length for the tokenized (image placeholders + instruction) prompt. Longer
|
||||
prompts are right-truncated.
|
||||
use_flash_attn (`bool`, *optional*, defaults to `True`):
|
||||
Whether to request FlashAttention in the InternVL3 backbone.
|
||||
action_head (`str`, *optional*, defaults to `"flowmatching"`):
|
||||
Identifier of the action-generation head architecture.
|
||||
embed_dim (`int`, *optional*, defaults to 896):
|
||||
Dimensionality of the fused vision-language token embeddings consumed by the action head.
|
||||
hidden_dim (`int`, *optional*, defaults to 1024):
|
||||
Hidden width of the action head's transformer layers.
|
||||
state_hidden_dim (`int`, *optional*, defaults to 1024):
|
||||
Hidden width of the state encoder inside the action head.
|
||||
num_heads (`int`, *optional*, defaults to 8):
|
||||
Number of attention heads in the action head's transformer layers.
|
||||
num_layers (`int`, *optional*, defaults to 8):
|
||||
Number of transformer layers in the action head.
|
||||
dropout (`float`, *optional*, defaults to 0.0):
|
||||
Dropout probability applied inside the action head.
|
||||
num_inference_timesteps (`int`, *optional*, defaults to 32):
|
||||
Number of integration steps used to sample an action chunk from the flow-matching head.
|
||||
num_categories (`int`, *optional*, defaults to 1):
|
||||
Number of embodiment categories the action head conditions on.
|
||||
return_cls_only (`bool`, *optional*, defaults to `False`):
|
||||
Whether the action head is conditioned on a single pooled VL token (the last non-padding token
|
||||
of the causal decoder) instead of the full fused token sequence.
|
||||
enable_gradient_checkpointing (`bool`, *optional*, defaults to `True`):
|
||||
Whether to enable gradient checkpointing on the VLM backbone to reduce memory usage.
|
||||
gradient_checkpointing_use_reentrant (`bool`, *optional*, defaults to `False`):
|
||||
Whether gradient checkpointing uses the reentrant autograd variant.
|
||||
finetune_vlm (`bool`, *optional*):
|
||||
Whether the whole VLM backbone is trainable. Defaulted from `training_stage` unless set
|
||||
explicitly with `apply_training_stage_defaults=False`. Must agree with the union of
|
||||
`finetune_language_model` and `finetune_vision_model` when those are set explicitly.
|
||||
finetune_language_model (`bool`, *optional*):
|
||||
Whether the VLM's language branch is trainable. Defaulted from `training_stage` unless set
|
||||
explicitly with `apply_training_stage_defaults=False`.
|
||||
finetune_vision_model (`bool`, *optional*):
|
||||
Whether the VLM's vision branch is trainable. Defaulted from `training_stage` unless set
|
||||
explicitly with `apply_training_stage_defaults=False`.
|
||||
finetune_action_head (`bool`, *optional*):
|
||||
Whether the flow-matching action head is trainable. Defaulted from `training_stage` unless set
|
||||
explicitly with `apply_training_stage_defaults=False`.
|
||||
apply_training_stage_defaults (`bool`, *optional*, defaults to `True`):
|
||||
Whether to reapply the `training_stage` defaults to the `finetune_*` flags after loading a
|
||||
checkpoint config, so a stage-2 run cannot silently inherit a stage-1 checkpoint's frozen-VLM
|
||||
flags. Set `False` to keep explicit finetuning flags.
|
||||
task_field (`str`, *optional*, defaults to `"task"`):
|
||||
Batch key holding the language instruction(s) passed to the VLM.
|
||||
embodiment_id_field (`str`, *optional*):
|
||||
Batch key holding an explicit per-sample embodiment id. Falls back to `"embodiment_id"`, then
|
||||
to `default_embodiment_id`, when unset or absent from the batch.
|
||||
default_embodiment_id (`int`, *optional*, defaults to 0):
|
||||
Embodiment id used when the batch carries none. Must be in `[0, num_categories)`.
|
||||
rtc_config (`RTCConfig`, *optional*):
|
||||
Real-Time Chunking guidance for asynchronous inference. `None` disables RTC.
|
||||
`lerobot-rollout --inference.type=rtc` sets this and calls `init_rtc_processor()`.
|
||||
optimizer_lr (`float`, *optional*, defaults to 1e-05):
|
||||
Learning rate used to build the default `AdamWConfig` optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.999)`):
|
||||
Adam beta coefficients for the default optimizer preset.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Adam epsilon for the default optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-05):
|
||||
Weight decay applied to the decayed parameter group in the default optimizer preset.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0):
|
||||
Gradient-norm clipping threshold for the default optimizer preset.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 300):
|
||||
Number of warmup steps for the default cosine-annealing-with-warmup scheduler preset.
|
||||
"""
|
||||
|
||||
training_stage: str = "stage1"
|
||||
# When True and the policy runs on CUDA, EVO1 wraps its own forward passes (training and
|
||||
# inference) in a bfloat16 autocast block, so its numerics do not depend on the dtype of any
|
||||
@@ -257,7 +108,6 @@ class Evo1Config(PreTrainedConfig):
|
||||
scheduler_warmup_steps: int = 300
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the VLM backbone/tokenizer configuration."""
|
||||
super().__post_init__()
|
||||
if self.training_stage not in {"stage1", "stage2"}:
|
||||
raise ValueError(
|
||||
@@ -350,7 +200,6 @@ class Evo1Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if self.input_features is None:
|
||||
self.input_features = {}
|
||||
if self.output_features is None:
|
||||
@@ -377,7 +226,6 @@ class Evo1Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -387,22 +235,18 @@ class Evo1Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineAnnealingWithWarmupSchedulerConfig(
|
||||
num_warmup_steps=self.scheduler_warmup_steps,
|
||||
)
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return [0]
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -33,43 +33,19 @@ from .evo1_model import Evo1Model
|
||||
|
||||
|
||||
class ActionSelectKwargs(TypedDict, total=False):
|
||||
"""Extra keyword arguments accepted by EVO1's `select_action`/`predict_action_chunk` for RTC inference.
|
||||
|
||||
**Attributes**:
|
||||
- **inference_delay** (`int | None`) -- Number of environment steps the previous inference call
|
||||
took, used by the RTC processor to blend the new chunk with `prev_chunk_left_over`.
|
||||
- **prev_chunk_left_over** (`Tensor | None`) -- Unconsumed tail of the previously predicted action
|
||||
chunk, blended with the new prediction for a smooth handoff.
|
||||
- **execution_horizon** (`int | None`) -- Number of steps of the new chunk that will actually be
|
||||
executed before the next inference call, used to weight the RTC blend.
|
||||
"""
|
||||
|
||||
inference_delay: int | None
|
||||
prev_chunk_left_over: Tensor | None
|
||||
execution_horizon: int | None
|
||||
|
||||
|
||||
class Evo1Policy(PreTrainedPolicy):
|
||||
"""EVO1 vision-language-action policy: an InternVL3 backbone with a flow-matching action head."""
|
||||
|
||||
config_class = Evo1Config
|
||||
name = "evo1"
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`]. EVO1 supports Real-Time Chunking."""
|
||||
return True
|
||||
|
||||
def __init__(self, config: Evo1Config, *, vlm_hub_kwargs: dict | None = None, **kwargs):
|
||||
"""Build the InternVL3 vision-language embedder and the flow-matching action head.
|
||||
|
||||
Args:
|
||||
config (`Evo1Config`):
|
||||
Policy configuration.
|
||||
vlm_hub_kwargs (`dict`, *optional*):
|
||||
Hub download options (`token`, `cache_dir`, `local_files_only`, `proxies`) forwarded to the
|
||||
VLM backbone's own `from_pretrained` call, as distinct from the ones used to load this
|
||||
policy's own checkpoint.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
|
||||
@@ -117,12 +93,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
strict: bool | None = None,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`].
|
||||
|
||||
Defaults `strict` to `True` instead of `False`, and additionally forwards `vlm_hub_kwargs` (or
|
||||
derives them from `token`, `cache_dir`, `local_files_only`, and `proxies`) to the InternVL3
|
||||
backbone's own `from_pretrained` call.
|
||||
"""
|
||||
if strict is None:
|
||||
strict = True
|
||||
vlm_hub_kwargs = kwargs.pop("vlm_hub_kwargs", None)
|
||||
@@ -200,11 +170,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
return nullcontext()
|
||||
|
||||
def get_optim_params(self) -> list[dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Splits parameters into a weight-decayed group and a no-decay group (biases and 1D/normalization
|
||||
parameters).
|
||||
"""
|
||||
decay, no_decay = [], []
|
||||
for name, param in self.named_parameters():
|
||||
if not param.requires_grad:
|
||||
@@ -221,7 +186,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
]
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the action queue used by `select_action`."""
|
||||
self._action_queue = deque([], maxlen=self.config.n_action_steps)
|
||||
|
||||
def _normalize_task_batch(self, batch: dict[str, Tensor | list[str] | str]) -> list[str]:
|
||||
@@ -398,12 +362,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
embedder.eval()
|
||||
|
||||
def train(self, mode: bool = True):
|
||||
"""Set training mode, keeping the VLM embedder in eval mode when its weights are frozen.
|
||||
|
||||
Args:
|
||||
mode (`bool`, *optional*, defaults to `True`):
|
||||
Whether to set training (`True`) or evaluation (`False`) mode.
|
||||
"""
|
||||
super().train(mode)
|
||||
self._keep_frozen_embedder_eval()
|
||||
return self
|
||||
@@ -494,12 +452,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
return sq_error.sum() / active.sum()
|
||||
|
||||
def forward(self, batch: dict[str, Tensor], reduction: str = "mean") -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Computes the flow-matching velocity-regression loss (squared error between the predicted and
|
||||
target velocity), masked to the active state/action dimensions and averaged per sample. Set
|
||||
`reduction="none"` to get the per-sample loss instead of the batch mean.
|
||||
"""
|
||||
prompts = self._normalize_task_batch(batch)
|
||||
image_batches, image_masks = self._collect_image_batches(batch)
|
||||
states, _state_mask = self._prepare_state(batch)
|
||||
@@ -534,12 +486,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs: Unpack[ActionSelectKwargs]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Accepts `ActionSelectKwargs`'s RTC-specific arguments (`inference_delay`, `prev_chunk_left_over`,
|
||||
`execution_horizon`), which are rejected unless `config.rtc_config` is set and
|
||||
`init_rtc_processor()` has been called.
|
||||
"""
|
||||
inference_delay = kwargs.get("inference_delay")
|
||||
prev_chunk_left_over = kwargs.get("prev_chunk_left_over")
|
||||
execution_horizon = kwargs.get("execution_horizon")
|
||||
@@ -576,11 +522,6 @@ class Evo1Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`. Real-Time Chunking is not supported
|
||||
here; use `predict_action_chunk` directly when `config.rtc_config` is enabled.
|
||||
"""
|
||||
assert not self._rtc_enabled(), (
|
||||
"RTC is not supported for select_action, use it with predict_action_chunk"
|
||||
)
|
||||
|
||||
@@ -381,25 +381,6 @@ def make_evo1_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Build the pre/post-processor pipelines for EVO1.
|
||||
|
||||
The preprocessor pads observation state and training actions to EVO1's fixed `max_state_dim` /
|
||||
`max_action_dim` widths (tracking the padding with an `action_mask`) before normalizing and moving the
|
||||
batch to `config.device`. The postprocessor unnormalizes predicted actions, crops them back down to the
|
||||
real action dimensionality, optionally binarizes the gripper channel, and moves the result to CPU.
|
||||
|
||||
Args:
|
||||
config (`Evo1Config`):
|
||||
EVO1 policy configuration.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]]`, *optional*):
|
||||
Per-feature normalization statistics, as produced by `LeRobotDatasetMetadata.stats`. Padded to
|
||||
`max_state_dim`/`max_action_dim` before being handed to the (un)normalizer steps.
|
||||
|
||||
Returns:
|
||||
`tuple[PolicyProcessorPipeline, PolicyProcessorPipeline]`: The preprocessor (batch of raw
|
||||
observations/actions -> model input) and postprocessor (model output -> environment action)
|
||||
pipelines.
|
||||
"""
|
||||
normalization_features = _evo1_normalization_features(config)
|
||||
action_features = _evo1_action_features(config)
|
||||
normalization_stats = _pad_evo1_stats(config, dataset_stats)
|
||||
|
||||
@@ -77,7 +77,8 @@ def _reconnect_relative_absolute_steps(
|
||||
|
||||
|
||||
def get_policy_class(name: str) -> type[PreTrainedPolicy]:
|
||||
"""Retrieves a policy class by its registered name.
|
||||
"""
|
||||
Retrieves a policy class by its registered name.
|
||||
|
||||
Resolution is convention-based: the draccus-registered config class of ``name`` is
|
||||
looked up, its ``configuration_*`` module path is rewritten to ``modeling_*``, and
|
||||
@@ -87,8 +88,7 @@ def get_policy_class(name: str) -> type[PreTrainedPolicy]:
|
||||
``@PreTrainedConfig.register_subclass``).
|
||||
|
||||
Args:
|
||||
name (`str`): The registered name of the policy (e.g. "act", "diffusion", "pi0").
|
||||
|
||||
name: The registered name of the policy (e.g. "act", "diffusion", "pi0").
|
||||
Returns:
|
||||
The policy class corresponding to the given name.
|
||||
|
||||
@@ -100,15 +100,16 @@ def get_policy_class(name: str) -> type[PreTrainedPolicy]:
|
||||
|
||||
|
||||
def make_policy_config(policy_type: str, **kwargs) -> PreTrainedConfig:
|
||||
"""Instantiates a policy configuration object based on the policy type.
|
||||
"""
|
||||
Instantiates a policy configuration object based on the policy type.
|
||||
|
||||
This factory function simplifies the creation of policy configuration objects by
|
||||
mapping a string identifier to the corresponding config class.
|
||||
|
||||
Args:
|
||||
policy_type (`str`): The registered type of the policy (any name registered via
|
||||
`@PreTrainedConfig.register_subclass`, e.g. "act", "diffusion", "pi0").
|
||||
kwargs (`Any`, *optional*): Keyword arguments to be passed to the configuration class constructor.
|
||||
policy_type: The registered type of the policy (any name registered via
|
||||
``@PreTrainedConfig.register_subclass``, e.g. "act", "diffusion", "pi0").
|
||||
**kwargs: Keyword arguments to be passed to the configuration class constructor.
|
||||
|
||||
Returns:
|
||||
An instance of a `PreTrainedConfig` subclass.
|
||||
@@ -124,21 +125,18 @@ def make_policy_config(policy_type: str, **kwargs) -> PreTrainedConfig:
|
||||
|
||||
|
||||
class ProcessorConfigKwargs(TypedDict, total=False):
|
||||
"""A TypedDict defining the keyword arguments for processor configuration.
|
||||
"""
|
||||
A TypedDict defining the keyword arguments for processor configuration.
|
||||
|
||||
This provides type hints for the optional arguments passed to `make_pre_post_processors`,
|
||||
improving code clarity and enabling static analysis.
|
||||
|
||||
**Attributes**:
|
||||
- **preprocessor_config_filename** (`str | None`) -- The filename for the preprocessor configuration.
|
||||
- **postprocessor_config_filename** (`str | None`) -- The filename for the postprocessor
|
||||
configuration.
|
||||
- **preprocessor_overrides** (`dict[str, Any] | None`) -- A dictionary of overrides for the
|
||||
preprocessor configuration.
|
||||
- **postprocessor_overrides** (`dict[str, Any] | None`) -- A dictionary of overrides for the
|
||||
postprocessor configuration.
|
||||
- **dataset_stats** (`dict[str, dict[str, torch.Tensor]] | None`) -- Dataset statistics for
|
||||
normalization.
|
||||
Attributes:
|
||||
preprocessor_config_filename: The filename for the preprocessor configuration.
|
||||
postprocessor_config_filename: The filename for the postprocessor configuration.
|
||||
preprocessor_overrides: A dictionary of overrides for the preprocessor configuration.
|
||||
postprocessor_overrides: A dictionary of overrides for the postprocessor configuration.
|
||||
dataset_stats: Dataset statistics for normalization.
|
||||
"""
|
||||
|
||||
preprocessor_config_filename: str | None
|
||||
@@ -158,7 +156,8 @@ def make_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Create or load pre- and post-processor pipelines for a given policy.
|
||||
"""
|
||||
Create or load pre- and post-processor pipelines for a given policy.
|
||||
|
||||
This function acts as a factory. It can either load existing processor pipelines
|
||||
from a pretrained path or create new ones from scratch based on the policy
|
||||
@@ -169,7 +168,6 @@ def make_pre_post_processors(
|
||||
policy_cfg: The configuration of the policy for which to create processors.
|
||||
pretrained_path: An optional path to load pretrained processor pipelines from.
|
||||
If provided, pipelines are loaded from this path.
|
||||
pretrained_revision: The Hub revision to load `pretrained_path` from, if it's a Hub repo id.
|
||||
**kwargs: Keyword arguments for processor configuration, as defined in
|
||||
`ProcessorConfigKwargs`.
|
||||
|
||||
@@ -246,7 +244,8 @@ def make_policy(
|
||||
rename_map: dict[str, str] | None = None,
|
||||
defer_weight_load: bool = False,
|
||||
) -> PreTrainedPolicy:
|
||||
"""Instantiate a policy model.
|
||||
"""
|
||||
Instantiate a policy model.
|
||||
|
||||
This factory function handles the logic of creating a policy, which requires
|
||||
determining the input and output feature shapes. These shapes can be derived
|
||||
@@ -256,13 +255,13 @@ def make_policy(
|
||||
Args:
|
||||
cfg (PreTrainedConfig): The configuration for the policy to be created. If
|
||||
`cfg.pretrained_path` is set, the policy will be loaded with weights from that path.
|
||||
ds_meta (LeRobotDatasetMetadata | None, *optional*): Dataset metadata used to infer feature shapes and
|
||||
ds_meta (LeRobotDatasetMetadata | None): Dataset metadata used to infer feature shapes and
|
||||
types. Also provides statistics for normalization layers.
|
||||
env_cfg (EnvConfig | None, *optional*): Environment configuration used to infer feature shapes and
|
||||
env_cfg (EnvConfig | None): Environment configuration used to infer feature shapes and
|
||||
types. One of `ds_meta` or `env_cfg` must be provided.
|
||||
rename_map (dict[str, str] | None, *optional*): Optional mapping of dataset or environment feature
|
||||
rename_map (dict[str, str] | None): Optional mapping of dataset or environment feature
|
||||
keys to match expected policy feature names (e.g., `"left"` → `"camera1"`).
|
||||
defer_weight_load (bool, *optional*, defaults to `False`): Build the exact policy `from_pretrained` would build — same
|
||||
defer_weight_load (bool): Build the exact policy `from_pretrained` would build — same
|
||||
config resolution, same stats-derived buffers, same device placement and eval mode —
|
||||
but skip the safetensors weight load. Used when resuming from a DCP checkpoint, whose
|
||||
sharded weights stream in after `accelerator.prepare()` (the distributed checkpoint
|
||||
@@ -409,7 +408,6 @@ def _get_policy_cls_from_policy_name(name: str) -> type[PreTrainedPolicy]:
|
||||
|
||||
Args:
|
||||
name: The name of the policy.
|
||||
|
||||
Returns:
|
||||
The policy class corresponding to the given name.
|
||||
"""
|
||||
@@ -465,10 +463,10 @@ def _make_processors_from_policy_config(
|
||||
dataset_stats: Dataset statistics for normalization.
|
||||
dataset_meta: Dataset metadata, forwarded only to factories that declare a
|
||||
``dataset_meta`` parameter (e.g. groot, molmoact2).
|
||||
|
||||
Returns:
|
||||
A tuple containing the input (pre-processor) and output (post-processor) pipelines.
|
||||
"""
|
||||
|
||||
policy_type = config.type
|
||||
function_name = f"make_{policy_type}_pre_post_processors"
|
||||
module_path = config.__class__.__module__.replace(
|
||||
|
||||
@@ -58,7 +58,6 @@ _FASTWAM_ACTION_BASE_COMPAT_KEYS = (
|
||||
|
||||
|
||||
def default_video_dit_config(action_dim: int) -> dict[str, Any]:
|
||||
"""Return the default kwargs dict for the video-generation DiT backbone, sized for `action_dim`."""
|
||||
return {
|
||||
"patch_size": [1, 2, 2],
|
||||
"in_dim": 48,
|
||||
@@ -82,7 +81,6 @@ def default_video_dit_config(action_dim: int) -> dict[str, Any]:
|
||||
|
||||
|
||||
def default_action_dit_config(action_dim: int) -> dict[str, Any]:
|
||||
"""Return the default kwargs dict for the action-generation DiT backbone, sized for `action_dim`."""
|
||||
return {
|
||||
"action_dim": action_dim,
|
||||
"hidden_dim": 1024,
|
||||
@@ -138,6 +136,7 @@ def _validate_wan_model_id(value: str, field_name: str) -> str:
|
||||
|
||||
def is_fastwam_base_compatible_config(config: FastWAMConfig) -> bool:
|
||||
"""Return whether `fastwam_base` partial weights can initialize this config."""
|
||||
|
||||
default_video_config = default_video_dit_config(config.action_dim)
|
||||
default_action_config = default_action_dit_config(config.action_dim)
|
||||
return all(
|
||||
@@ -154,129 +153,30 @@ def is_fastwam_base_compatible_config(config: FastWAMConfig) -> bool:
|
||||
class FastWAMConfig(PreTrainedConfig):
|
||||
"""Configuration for the FastWAM LeRobot policy.
|
||||
|
||||
FastWAM adapts the Wan2.2 video-diffusion backbone into a robot policy: a video expert and an action
|
||||
expert are jointly trained (or fine-tuned) as a Mixture-of-Transformers, sharing attention over a
|
||||
predicted future video and the corresponding action chunk.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy.
|
||||
input_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Input feature specification, keyed by feature name. `__post_init__` builds a synthetic
|
||||
single-image default at `image_size` when left unset; `set_dataset_feature_metadata` later
|
||||
replaces it with the dataset's real per-camera keys.
|
||||
output_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Output feature specification, keyed by feature name. `__post_init__` builds a default `action`
|
||||
feature of shape `(action_dim,)` when left unset.
|
||||
device (`str`, *optional*):
|
||||
Torch device to run the policy on, e.g. `"cuda"` or `"cpu"`. Auto-selected when unset or
|
||||
unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str`, *optional*):
|
||||
Hub repository id to push the policy to.
|
||||
private (`bool`, *optional*):
|
||||
Whether the pushed Hub repository is private.
|
||||
tags (`list[str]`, *optional*):
|
||||
Tags to attach to the policy on the Hub.
|
||||
license (`str`, *optional*):
|
||||
License identifier for the policy on the Hub.
|
||||
pretrained_path (`Path`, *optional*):
|
||||
Repo id or local directory of pretrained weights saved with `save_pretrained`. Auto-populated
|
||||
from `base_model_id` when the DiT configs are `fastwam_base`-compatible; otherwise left unset
|
||||
to initialize from scratch.
|
||||
pretrained_revision (`str`, *optional*):
|
||||
Hub revision to pin when loading `pretrained_path`.
|
||||
action_dim (`int`, *optional*, defaults to 7):
|
||||
Number of scalar action channels per timestep.
|
||||
proprio_dim (`int`, *optional*, defaults to 8):
|
||||
Number of proprioception channels used as an extra text-context token. `None` disables proprio
|
||||
conditioning.
|
||||
action_horizon (`int`, *optional*, defaults to 32):
|
||||
Number of actions predicted by one policy call.
|
||||
n_action_steps (`int`, *optional*, defaults to 32):
|
||||
Number of actions from a predicted chunk that are actually executed before re-querying the
|
||||
policy. Must not exceed `action_horizon`.
|
||||
num_video_frames (`int`, *optional*, defaults to 33):
|
||||
Raw video sampling window, in dataset frames. The model actually operates on
|
||||
`model_video_frames` frames after subsampling by `action_video_freq_ratio`.
|
||||
action_video_freq_ratio (`int`, *optional*, defaults to 4):
|
||||
Actions are sampled at this multiple of the video frame rate. Video frames are taken every
|
||||
`action_video_freq_ratio`-th raw frame, so the model sees `(num_video_frames - 1) // ratio + 1`
|
||||
frames spanning the same time window as `action_horizon` actions.
|
||||
image_size (`tuple[int, int]`, *optional*, defaults to `(224, 448)`):
|
||||
Concatenated image size as `(height, width)`, shared across every camera view.
|
||||
context_len (`int`, *optional*, defaults to 128):
|
||||
Maximum text embedding token length.
|
||||
model_id (`str`, *optional*, defaults to `"Wan-AI/Wan2.2-TI2V-5B"`):
|
||||
Hub id (or local path) of the Wan2.2 video-diffusion backbone.
|
||||
tokenizer_model_id (`str`, *optional*, defaults to `"google/umt5-xxl"`):
|
||||
Hub id of the UMT5 tokenizer.
|
||||
text_encoder_model_id (`str`, *optional*, defaults to `"Wan-AI/Wan2.2-TI2V-5B-Diffusers"`):
|
||||
Hub id of the frozen UMT5 text encoder and VAE used for text/video conditioning.
|
||||
base_model_id (`str`, *optional*, defaults to `"lerobot/fastwam_base"`):
|
||||
Hub id of the FastWAM base checkpoint used to auto-populate `pretrained_path` when the DiT
|
||||
configs are compatible with it. `None` disables this auto-loading.
|
||||
tokenizer_max_len (`int`, *optional*, defaults to 128):
|
||||
Maximum token length passed to the tokenizer.
|
||||
load_text_encoder (`bool`, *optional*, defaults to `True`):
|
||||
Whether to load the frozen UMT5 text encoder. Disable when the batch always supplies
|
||||
precomputed `context`/`context_mask`.
|
||||
mot_checkpoint_mixed_attn (`bool`, *optional*, defaults to `False`):
|
||||
Whether the Mixture-of-Transformers module checkpoints its mixed video/action attention.
|
||||
torch_dtype (`str`, *optional*, defaults to `"bfloat16"`):
|
||||
Dtype the Wan backbone and action expert are built and run in.
|
||||
prompt_template (`str`, *optional*, defaults to `"A video recorded from a robot's point of view executing the following instruction: {task}"`):
|
||||
Template the raw `task` string is formatted into before text encoding.
|
||||
num_inference_steps (`int`, *optional*, defaults to 10):
|
||||
Number of denoising steps used at inference time.
|
||||
inference_seed (`int`, *optional*, defaults to 42):
|
||||
Random seed for the inference noise sampler. `None` samples fresh noise every call.
|
||||
rand_device (`str`, *optional*, defaults to `"cpu"`):
|
||||
Device the inference noise sampler draws from.
|
||||
text_cfg_scale (`float`, *optional*, defaults to 1.0):
|
||||
Classifier-free-guidance scale applied against `negative_prompt` at inference time.
|
||||
negative_prompt (`str`, *optional*, defaults to `""`):
|
||||
Negative prompt used for classifier-free guidance.
|
||||
sigma_shift (`float`, *optional*):
|
||||
Overrides the diffusion schedule's sigma shift at inference time. `None` uses the scheduler's
|
||||
own shift.
|
||||
tiled (`bool`, *optional*, defaults to `False`):
|
||||
Whether to run the Wan VAE in tiled mode to reduce memory use.
|
||||
fp32_attention (`bool`, *optional*, defaults to `True`):
|
||||
Whether the video and action DiT experts compute attention in fp32.
|
||||
use_gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to enable activation checkpointing in both DiT experts, trading compute for memory.
|
||||
Propagated into `video_dit_config` and `action_dit_config`.
|
||||
freeze_video_expert (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the ~5B Wan video expert so only the action expert and proprio encoder
|
||||
train, cutting the AdamW optimizer footprint substantially. Also set `loss.lambda_video=0` to
|
||||
skip the now-gradient-free video loss compute.
|
||||
toggle_action_dimensions (`list[int]`, *optional*):
|
||||
Action dimensions the postprocessor flips between two fixed values, for LIBERO-style toggle
|
||||
actions such as the gripper. Empty disables the toggle.
|
||||
video_scheduler (`dict[str, float | int]`, *optional*):
|
||||
Train/inference shift and step-count settings for the video diffusion scheduler.
|
||||
action_scheduler (`dict[str, float | int]`, *optional*):
|
||||
Train/inference shift and step-count settings for the action diffusion scheduler.
|
||||
loss (`dict[str, float]`, *optional*):
|
||||
Per-term loss weights, keyed by `"lambda_video"` and `"lambda_action"`.
|
||||
video_dit_config (`dict[str, Any]`, *optional*):
|
||||
Wan video expert architecture config. Built from `default_video_dit_config(action_dim)` when
|
||||
left unset.
|
||||
action_dit_config (`dict[str, Any]`, *optional*):
|
||||
Action expert architecture config. Built from `default_action_dit_config(action_dim)` when
|
||||
left unset.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps each `FeatureType` to the `NormalizationMode` used to normalize/unnormalize it.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Learning rate used to build the default `AdamWConfig` optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.01):
|
||||
Weight decay for the default optimizer preset.
|
||||
action_dim (int): Number of scalar action channels per timestep.
|
||||
proprio_dim (int | None): Number of proprioception channels used as an
|
||||
extra text-context token. `None` disables proprio conditioning.
|
||||
action_horizon (int): Number of actions predicted by one policy call.
|
||||
num_video_frames (int): Raw video sampling window (in dataset frames). The
|
||||
model actually operates on `model_video_frames` frames after subsampling
|
||||
by `action_video_freq_ratio`.
|
||||
action_video_freq_ratio (int): Actions are sampled at this multiple of the
|
||||
video frame rate. Video frames are taken every `action_video_freq_ratio`-th
|
||||
raw frame, so the model sees `(num_video_frames - 1) // ratio + 1` frames
|
||||
spanning the same time window as `action_horizon` actions (ratio actions
|
||||
per video frame).
|
||||
image_size (tuple[int, int]): Concatenated image size as `(height, width)`.
|
||||
context_len (int): Maximum text embedding token length.
|
||||
video_dit_config (dict[str, Any] | None): Wan video expert config.
|
||||
action_dit_config (dict[str, Any] | None): Action expert config.
|
||||
use_gradient_checkpointing (bool): Enable activation checkpointing in both DiT
|
||||
experts (trades compute for memory; propagated into the DiT configs).
|
||||
freeze_video_expert (bool): Freeze the ~5B Wan video expert
|
||||
(`model.video_expert`) so only the action expert + proprio encoder train.
|
||||
Cuts the AdamW optimizer footprint substantially; the video expert keeps its
|
||||
pretrained weights. (If enabled, also set `loss.lambda_video=0` to skip the
|
||||
now-gradient-free video loss compute.)
|
||||
"""
|
||||
|
||||
n_obs_steps: int = 1
|
||||
@@ -332,7 +232,6 @@ class FastWAMConfig(PreTrainedConfig):
|
||||
optimizer_weight_decay: float = 1.0e-2
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the DiT/video backbone configuration."""
|
||||
super().__post_init__()
|
||||
self.image_size = tuple(self.image_size)
|
||||
self.model_id = _validate_wan_model_id(self.model_id, "model_id")
|
||||
@@ -381,11 +280,9 @@ class FastWAMConfig(PreTrainedConfig):
|
||||
self.pretrained_path = pretrained_path
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(lr=self.optimizer_lr, weight_decay=self.optimizer_weight_decay)
|
||||
|
||||
def get_scheduler_preset(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return None
|
||||
|
||||
def set_dataset_feature_metadata(self, dataset_features: dict[str, Any]) -> None:
|
||||
@@ -420,7 +317,6 @@ class FastWAMConfig(PreTrainedConfig):
|
||||
self.validate_features()
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if self.action_dim <= 0:
|
||||
raise ValueError(f"`action_dim` must be positive, got {self.action_dim}.")
|
||||
if self.action_horizon <= 0:
|
||||
@@ -481,16 +377,12 @@ class FastWAMConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def model_video_frames(self) -> int:
|
||||
"""Number of video frames the model actually operates on.
|
||||
|
||||
Computed by subsampling the raw `num_video_frames` window by `action_video_freq_ratio` (e.g.
|
||||
33 -> 9).
|
||||
"""
|
||||
"""Number of video frames the model actually operates on, after subsampling the
|
||||
raw `num_video_frames` window by `action_video_freq_ratio` (e.g. 33 -> 9)."""
|
||||
return (self.num_video_frames - 1) // self.action_video_freq_ratio + 1
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
# Load the video frames the model is supervised on: the future window subsampled by
|
||||
# action_video_freq_ratio (e.g. [0, 4, 8, ..., 32] -> 9 frames). Each video frame is
|
||||
# thus `action_video_freq_ratio` actions apart, while actions load at the full rate
|
||||
@@ -500,10 +392,8 @@ class FastWAMConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.action_horizon))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -45,6 +45,11 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
arbitrary boolean ``[query, key]`` masks that the FlashAttention varlen API cannot express;
|
||||
installing ``flash-attn`` has no effect on the FastWAM path. (SDPA may still dispatch to
|
||||
PyTorch's own flash/mem-efficient/math kernel internally, unrelated to the ``flash-attn`` package.)
|
||||
|
||||
Args:
|
||||
config (FastWAMConfig): FastWAM policy configuration.
|
||||
dataset_stats (dict[str, dict[str, Tensor]] | None): Optional LeRobot
|
||||
dataset statistics passed by the training/evaluation stack.
|
||||
"""
|
||||
|
||||
config_class = FastWAMConfig
|
||||
@@ -59,17 +64,6 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
dataset_stats: dict[str, dict[str, Tensor]] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Build the FastWAM core model (video expert, action expert, and MoT router).
|
||||
|
||||
Args:
|
||||
config (`FastWAMConfig`):
|
||||
FastWAM policy configuration.
|
||||
dataset_stats (`dict[str, dict[str, Tensor]]`, *optional*):
|
||||
LeRobot dataset statistics passed by the training/evaluation stack. Accepted for
|
||||
signature compatibility with other policies but not otherwise used here.
|
||||
kwargs: Additional keyword arguments (e.g. `dataset_meta`) forwarded by `make_policy` or
|
||||
`from_pretrained`; accepted and ignored.
|
||||
"""
|
||||
# FastWAM's Wan2.2 backbone needs transformers (UMT5 text encoder/tokenizer) and
|
||||
# diffusers (Wan VAE), both behind the `fastwam` extra. Fail fast with an actionable
|
||||
# message in base installs rather than deep in Wan component construction.
|
||||
@@ -146,12 +140,6 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
return model
|
||||
|
||||
def get_optim_params(self) -> list[Tensor]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Returns a flat list of trainable tensors (DiT parameters plus the proprio encoder's, when
|
||||
present) rather than a param-group dict, so parameters frozen via `freeze_video_expert` are
|
||||
excluded.
|
||||
"""
|
||||
# Return the trainable tensors directly (a single param group). The optimizer
|
||||
# builder wraps these in a param group; returning a bare {"params": [...]} dict
|
||||
# instead would make `list(...)` yield the key string "params".
|
||||
@@ -164,7 +152,6 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
return [p for p in params if p.requires_grad]
|
||||
|
||||
def reset(self) -> None:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the action queue used by `select_action`."""
|
||||
self._action_queue: deque[Tensor] = deque([], maxlen=self.config.n_action_steps)
|
||||
|
||||
def _batch_to_training_sample(self, batch: dict[str, Tensor]) -> dict[str, Tensor]:
|
||||
@@ -200,24 +187,36 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
return sample
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict[str, Any]]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
"""Compute FastWAM training loss for a LeRobot batch.
|
||||
|
||||
Accepts either FastWAM-native batch keys (`video`, `action`, `context`, `context_mask`) or
|
||||
standard LeRobot keys (`observation.images.*`, `observation.state`, `action`, `action_is_pad`),
|
||||
which are adapted internally. The metrics dict includes per-term losses such as `loss_video` and
|
||||
`loss_action`.
|
||||
Args:
|
||||
batch (dict[str, Tensor]): Batch containing FastWAM-ready keys
|
||||
(`video`, `action`, `context`, `context_mask`) or LeRobot keys
|
||||
that can be adapted (`observation.images.*`, `observation.state`,
|
||||
`action`, `action_is_pad`).
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, dict[str, Any]]: The scalar loss to backprop, and a dict of
|
||||
logging metrics (e.g. `loss_video`, `loss_action`) — the `(loss, output_dict)`
|
||||
contract the LeRobot training loop expects.
|
||||
"""
|
||||
|
||||
sample = self._batch_to_training_sample(batch)
|
||||
loss, metrics = self.model.training_loss(sample)
|
||||
return loss, dict(metrics or {})
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **_: Any) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
"""Predict a chunk of actions from the current FastWAM observation.
|
||||
|
||||
Accepts an inference batch with `input_image` or image-observation keys, plus a `context`/
|
||||
`context_mask` pair or a `prompt`. Returns a chunk of shape `[B, action_horizon, action_dim]`.
|
||||
Args:
|
||||
batch (dict[str, Tensor]): Inference batch with `input_image` or
|
||||
image observation keys, plus `context/context_mask` or `prompt`.
|
||||
|
||||
Returns:
|
||||
Tensor: Action chunk with shape `[B, action_horizon, action_dim]`.
|
||||
"""
|
||||
|
||||
self.eval()
|
||||
infer_kwargs = _batch_to_infer_kwargs(batch=batch, config=self.config)
|
||||
batch_size = _infer_kwargs_batch_size(infer_kwargs)
|
||||
@@ -239,7 +238,6 @@ class FastWAMPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], **kwargs: Any) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Uses an action queue populated by `predict_action_chunk`."""
|
||||
self.eval()
|
||||
if len(self._action_queue) == 0:
|
||||
actions = self.predict_action_chunk(batch, **kwargs)[:, : self.config.n_action_steps]
|
||||
|
||||
@@ -73,13 +73,14 @@ def make_fastwam_pre_post_processors(
|
||||
Args:
|
||||
config (FastWAMConfig): Policy configuration controlling device and
|
||||
normalization feature metadata.
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None, *optional*): Optional
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None): Optional
|
||||
LeRobot dataset statistics used by normalization processors.
|
||||
|
||||
Returns:
|
||||
tuple[PolicyProcessorPipeline, PolicyProcessorPipeline]: Input and
|
||||
output processor pipelines discoverable by LeRobot.
|
||||
"""
|
||||
|
||||
# NOTE: no visual normalization here. VISUAL is IDENTITY (see configuration_fastwam.normalization_mapping)
|
||||
# — images pass through in [0, 1] and the model maps them to the Wan VAE's [-1, 1] at the encode
|
||||
# boundary. This is deliberate: `lerobot_train.py` overrides the normalizer stats with
|
||||
|
||||
@@ -26,7 +26,7 @@ def is_image_feature(key: str) -> bool:
|
||||
"""Check if a feature key represents an image feature.
|
||||
|
||||
Args:
|
||||
key (`str`): The feature key to check.
|
||||
key: The feature key to check
|
||||
|
||||
Returns:
|
||||
True if the key represents an image feature, False otherwise
|
||||
@@ -54,8 +54,6 @@ class ConcurrencyConfig:
|
||||
|
||||
@dataclass
|
||||
class ActorLearnerConfig:
|
||||
"""Actor-learner distributed architecture settings (network address, weight-push frequency)."""
|
||||
|
||||
learner_host: str = "127.0.0.1"
|
||||
learner_port: int = 50051
|
||||
policy_parameters_push_frequency: int = 4
|
||||
@@ -64,8 +62,6 @@ class ActorLearnerConfig:
|
||||
|
||||
@dataclass
|
||||
class CriticNetworkConfig:
|
||||
"""MLP architecture settings for the critic network(s)."""
|
||||
|
||||
hidden_dims: list[int] = field(default_factory=lambda: [256, 256])
|
||||
activate_final: bool = True
|
||||
final_activation: str | None = None
|
||||
@@ -73,16 +69,12 @@ class CriticNetworkConfig:
|
||||
|
||||
@dataclass
|
||||
class ActorNetworkConfig:
|
||||
"""MLP architecture settings for the actor network."""
|
||||
|
||||
hidden_dims: list[int] = field(default_factory=lambda: [256, 256])
|
||||
activate_final: bool = True
|
||||
|
||||
|
||||
@dataclass
|
||||
class PolicyConfig:
|
||||
"""Gaussian-policy output-head settings (tanh squashing, std clamping)."""
|
||||
|
||||
use_tanh_squash: bool = True
|
||||
std_min: float = 1e-5
|
||||
std_max: float = 10.0
|
||||
@@ -102,95 +94,9 @@ class GaussianActorConfig(PreTrainedConfig):
|
||||
logic live on the algorithm side (see ``lerobot.rl.algorithms.sac``).
|
||||
|
||||
CLI: ``--policy.type=gaussian_actor``.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back). This policy predicts a single action from a single step, so
|
||||
this is not expected to be changed from 1.
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str`, *optional*, defaults to `"cpu"`):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the
|
||||
policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to mean/std normalization for visual features and min/max normalization for
|
||||
state, environment, and action features.
|
||||
dataset_stats (`dict[str, dict[str, list[float]]] | None`, *optional*):
|
||||
Statistics used to normalize image, state, and action features. Defaults to placeholder
|
||||
values; normally overridden with statistics computed from the actual training dataset.
|
||||
storage_device (`str`, *optional*, defaults to `"cpu"`):
|
||||
Device on which a copy of the model's parameters is kept for transport between the actor and
|
||||
learner processes in the actor-learner architecture.
|
||||
vision_encoder_name (`str | None`, *optional*):
|
||||
Name of a pretrained vision encoder to use for image observations, e.g.
|
||||
`"lerobot/resnet10"` for the HIL-SERL ResNet10 encoder. `None` (the default) uses a
|
||||
lightweight from-scratch CNN encoder instead.
|
||||
freeze_vision_encoder (`bool`, *optional*, defaults to `True`):
|
||||
Whether to freeze the vision encoder's parameters during training.
|
||||
image_encoder_hidden_dim (`int`, *optional*, defaults to 32):
|
||||
Hidden dimension size for the from-scratch image encoder (unused when `vision_encoder_name`
|
||||
is set).
|
||||
shared_encoder (`bool`, *optional*, defaults to `True`):
|
||||
Whether the actor and critic(s) share the same observation encoder instance.
|
||||
num_discrete_actions (`int | None`, *optional*):
|
||||
Number of discrete actions appended to the continuous action output, e.g. for a gripper
|
||||
open/close action. `None` disables the discrete critic and action head.
|
||||
image_embedding_pooling_dim (`int`, *optional*, defaults to 8):
|
||||
Number of learned spatial pooling features per image, used by the image encoder's spatial
|
||||
embedding layer.
|
||||
state_encoder_hidden_dim (`int`, *optional*, defaults to 256):
|
||||
Hidden dimension size for the state encoder.
|
||||
latent_dim (`int`, *optional*, defaults to 256):
|
||||
Dimension of the observation encoder's output latent space.
|
||||
online_steps (`int`, *optional*, defaults to 1000000):
|
||||
Number of steps to run during online training.
|
||||
online_buffer_capacity (`int`, *optional*, defaults to 100000):
|
||||
Capacity of the online replay buffer.
|
||||
offline_buffer_capacity (`int`, *optional*, defaults to 100000):
|
||||
Capacity of the offline replay buffer.
|
||||
async_prefetch (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use asynchronous prefetching for the replay buffers.
|
||||
online_step_before_learning (`int`, *optional*, defaults to 100):
|
||||
Number of steps to collect before online learning starts.
|
||||
actor_learner_config (`ActorLearnerConfig`, *optional*):
|
||||
Transport configuration (host, port, push frequency, queue timeout) for the actor-learner
|
||||
architecture.
|
||||
concurrency (`ConcurrencyConfig`, *optional*):
|
||||
Concurrency configuration (threads or processes) for the actor and learner.
|
||||
actor_network_kwargs (`ActorNetworkConfig`, *optional*):
|
||||
Architecture configuration (hidden dimensions, final activation) for the actor network.
|
||||
policy_kwargs (`PolicyConfig`, *optional*):
|
||||
Configuration for the Gaussian policy head (tanh squashing, std bounds, final-layer init
|
||||
scale).
|
||||
discrete_critic_network_kwargs (`CriticNetworkConfig`, *optional*):
|
||||
Architecture configuration (hidden dimensions, final activation) for the discrete critic
|
||||
network.
|
||||
"""
|
||||
|
||||
# Mapping of feature types to normalization modes
|
||||
normalization_mapping: dict[str, NormalizationMode] = field(
|
||||
default_factory=lambda: {
|
||||
"VISUAL": NormalizationMode.MEAN_STD,
|
||||
@@ -200,6 +106,7 @@ class GaussianActorConfig(PreTrainedConfig):
|
||||
}
|
||||
)
|
||||
|
||||
# Statistics for normalizing different types of inputs
|
||||
dataset_stats: dict[str, dict[str, list[float]]] | None = field(
|
||||
default_factory=lambda: {
|
||||
OBS_IMAGE: {
|
||||
@@ -218,42 +125,60 @@ class GaussianActorConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
# Architecture specifics
|
||||
# Device to run the model on (e.g., "cuda", "cpu")
|
||||
device: str = "cpu"
|
||||
# Device to store the model on
|
||||
storage_device: str = "cpu"
|
||||
# Name of the vision encoder model (Set to "lerobot/resnet10" for hil serl resnet10)
|
||||
vision_encoder_name: str | None = None
|
||||
# Whether to freeze the vision encoder during training
|
||||
freeze_vision_encoder: bool = True
|
||||
# Hidden dimension size for the image encoder
|
||||
image_encoder_hidden_dim: int = 32
|
||||
# Whether to use a shared encoder for actor and critic
|
||||
shared_encoder: bool = True
|
||||
# Number of discrete actions, eg for gripper actions
|
||||
num_discrete_actions: int | None = None
|
||||
# Dimension of the image embedding pooling
|
||||
image_embedding_pooling_dim: int = 8
|
||||
|
||||
# Encoder architecture
|
||||
# Hidden dimension size for the state encoder
|
||||
state_encoder_hidden_dim: int = 256
|
||||
# Dimension of the latent space
|
||||
latent_dim: int = 256
|
||||
|
||||
# Online training (TODO(Khalil): relocate to TrainRLServerPipelineConfig)
|
||||
# Number of steps for online training
|
||||
online_steps: int = 1000000
|
||||
# Capacity of the online replay buffer
|
||||
online_buffer_capacity: int = 100000
|
||||
# Capacity of the offline replay buffer
|
||||
offline_buffer_capacity: int = 100000
|
||||
# Whether to use asynchronous prefetching for the buffers
|
||||
async_prefetch: bool = False
|
||||
# Number of steps before learning starts
|
||||
online_step_before_learning: int = 100
|
||||
|
||||
# Actor-learner transport (TODO(Khalil): relocate to TrainRLServerPipelineConfig).
|
||||
# Configuration for actor-learner architecture
|
||||
actor_learner_config: ActorLearnerConfig = field(default_factory=ActorLearnerConfig)
|
||||
# Configuration for concurrency settings (you can use threads or processes for the actor and learner)
|
||||
concurrency: ConcurrencyConfig = field(default_factory=ConcurrencyConfig)
|
||||
|
||||
# Network architecture
|
||||
# Configuration for the actor network architecture
|
||||
actor_network_kwargs: ActorNetworkConfig = field(default_factory=ActorNetworkConfig)
|
||||
# Configuration for the policy parameters (Gaussian head)
|
||||
policy_kwargs: PolicyConfig = field(default_factory=PolicyConfig)
|
||||
# Configuration for the discrete critic network
|
||||
discrete_critic_network_kwargs: CriticNetworkConfig = field(default_factory=CriticNetworkConfig)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates actor/critic network and learner configuration."""
|
||||
super().__post_init__()
|
||||
# Any validation specific to GaussianActor configuration
|
||||
|
||||
def get_optimizer_preset(self) -> MultiAdamConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
# Default learning rate used to satisfy the abstract ``get_optimizer_preset()``
|
||||
# contract from ``PreTrainedConfig``. The actual optimizers used during RL
|
||||
# training are built by ``SACAlgorithm.make_optimizers_and_scheduler()`` from
|
||||
@@ -270,11 +195,9 @@ class GaussianActorConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return None
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
has_image = any(is_image_feature(key) for key in self.input_features)
|
||||
has_state = OBS_STATE in self.input_features
|
||||
|
||||
@@ -288,20 +211,16 @@ class GaussianActorConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def image_features(self) -> list[str]:
|
||||
"""The names of the input features that are images."""
|
||||
return [key for key in self.input_features if is_image_feature(key)]
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return None # SAC typically predicts one action at a time
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -35,14 +35,6 @@ DISCRETE_DIMENSION_INDEX = -1 # Gripper is always the last dimension
|
||||
class GaussianActorPolicy(
|
||||
PreTrainedPolicy,
|
||||
):
|
||||
"""Tanh-squashed diagonal Gaussian actor policy for SAC and related maximum-entropy continuous-control
|
||||
algorithms.
|
||||
|
||||
This policy only implements the actor (and its observation encoder) plus an optional discrete-action
|
||||
critic head; the Q-critics, temperature, and Bellman-update logic live on the algorithm side (see
|
||||
`lerobot.rl.algorithms.sac`).
|
||||
"""
|
||||
|
||||
config_class = GaussianActorConfig
|
||||
name = "gaussian_actor"
|
||||
|
||||
@@ -50,11 +42,6 @@ class GaussianActorPolicy(
|
||||
self,
|
||||
config: GaussianActorConfig | None = None,
|
||||
):
|
||||
"""Build the observation encoder(s), the Gaussian actor network, and the optional discrete critic.
|
||||
|
||||
Args:
|
||||
config (GaussianActorConfig): The policy configuration.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
self.config = config
|
||||
@@ -66,12 +53,6 @@ class GaussianActorPolicy(
|
||||
self._init_discrete_critic()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Returns only the `"actor"` parameter group, excluding the shared encoder's parameters when
|
||||
`shared_encoder` is enabled. The critic, encoder, and temperature parameters are optimized
|
||||
separately by the SAC algorithm.
|
||||
"""
|
||||
optim_params = {
|
||||
"actor": [
|
||||
p
|
||||
@@ -82,30 +63,20 @@ class GaussianActorPolicy(
|
||||
return optim_params
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. This policy holds no episode-scoped state,
|
||||
so this is a no-op.
|
||||
"""
|
||||
"""Reset the policy"""
|
||||
pass
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Not supported: this policy predicts a single action per call rather than a chunk of actions, and
|
||||
calling this always raises `NotImplementedError`.
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
raise NotImplementedError(
|
||||
"GaussianActorPolicy does not support action chunking. It returns single actions!"
|
||||
)
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
"""Select action for inference/evaluation"""
|
||||
|
||||
Samples one action directly from the actor network, re-using cached image features from the
|
||||
shared encoder when available, and appends an argmax discrete action (e.g. a gripper command)
|
||||
when `num_discrete_actions` is set.
|
||||
"""
|
||||
observations_features = None
|
||||
if self.shared_encoder and self.actor.encoder.has_images:
|
||||
observations_features = self.actor.encoder.get_cached_image_features(batch)
|
||||
@@ -125,19 +96,15 @@ class GaussianActorPolicy(
|
||||
return actions
|
||||
|
||||
def forward(self, batch: dict[str, Tensor | dict[str, Tensor]]) -> dict[str, Tensor]:
|
||||
"""Actor forward pass: sample actions and return their log-probabilities.
|
||||
|
||||
Deviates from the base contract: rather than returning a training loss, this returns the actor's
|
||||
sampled actions, log-probabilities, and means directly. Loss computation and the Bellman update
|
||||
live on the algorithm side (see `lerobot.rl.algorithms.sac`).
|
||||
"""Actor forward pass: sample actions and return log-probabilities.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor | dict[str, Tensor]]): A flat observation dict, or a training dict
|
||||
containing `"state"` (observations) and optionally `"observation_feature"`
|
||||
batch: A flat observation dict, or a training dict containing
|
||||
``"state"`` (observations) and optionally ``"observation_feature"``
|
||||
(pre-computed encoder features).
|
||||
|
||||
Returns:
|
||||
dict[str, Tensor]: Dict with `"action"`, `"log_prob"`, and `"action_mean"` tensors.
|
||||
Dict with ``"action"``, ``"log_prob"``, and ``"action_mean"`` tensors.
|
||||
"""
|
||||
observations = batch.get("state", batch)
|
||||
observation_features = batch.get("observation_feature") if isinstance(batch, dict) else None
|
||||
@@ -344,10 +311,10 @@ class MLP(nn.Module):
|
||||
Arguments:
|
||||
input_dim (int): Size of input feature dimension.
|
||||
hidden_dims (list[int]): Sizes for each hidden layer.
|
||||
activations (Callable or str, *optional*, defaults to `SiLU()`): Activation to apply between layers.
|
||||
activate_final (bool, *optional*, defaults to `False`): Whether to apply activation at the final layer.
|
||||
dropout_rate (Optional[float], *optional*): Dropout probability applied before normalization and activation.
|
||||
final_activation (Optional[Callable or str], *optional*): Activation for the final layer when `activate_final` is True.
|
||||
activations (Callable or str): Activation to apply between layers.
|
||||
activate_final (bool): Whether to apply activation at the final layer.
|
||||
dropout_rate (Optional[float]): Dropout probability applied before normalization and activation.
|
||||
final_activation (Optional[Callable or str]): Activation for the final layer when `activate_final` is True.
|
||||
|
||||
For each layer, `in_dim` is updated to the previous `out_dim`. All constructed modules are
|
||||
stored in `self.net` as an `nn.Sequential` container.
|
||||
@@ -595,7 +562,8 @@ def orthogonal_init():
|
||||
|
||||
class SpatialLearnedEmbeddings(nn.Module):
|
||||
def __init__(self, height, width, channel, num_features=8):
|
||||
"""PyTorch implementation of learned spatial embeddings
|
||||
"""
|
||||
PyTorch implementation of learned spatial embeddings
|
||||
|
||||
Args:
|
||||
height: Spatial height of input features
|
||||
@@ -614,7 +582,8 @@ class SpatialLearnedEmbeddings(nn.Module):
|
||||
nn.init.kaiming_normal_(self.kernel, mode="fan_in", nonlinearity="linear")
|
||||
|
||||
def forward(self, features):
|
||||
"""Forward pass for spatial embedding
|
||||
"""
|
||||
Forward pass for spatial embedding
|
||||
|
||||
Args:
|
||||
features: Input tensor of shape [B, C, H, W] where B is batch size,
|
||||
@@ -622,6 +591,7 @@ class SpatialLearnedEmbeddings(nn.Module):
|
||||
Returns:
|
||||
Output tensor of shape [B, C*F] where F is the number of features
|
||||
"""
|
||||
|
||||
features_expanded = features.unsqueeze(-1) # [B, C, H, W, 1]
|
||||
kernel_expanded = self.kernel.unsqueeze(0) # [1, C, H, W, F]
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ def make_gaussian_actor_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the Gaussian actor policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the Gaussian actor policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -48,8 +49,8 @@ def make_gaussian_actor_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`GaussianActorConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the tanh-Gaussian policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
|
||||
@@ -74,11 +74,6 @@ _GROOT_ACTION_DECODE_TRANSFORM_ALIASES = {
|
||||
|
||||
|
||||
def normalize_groot_model_version(model_version: str) -> str:
|
||||
"""Resolve `model_version` to a canonical GR00T version string.
|
||||
|
||||
Raises:
|
||||
ValueError: If `model_version` isn't a recognized alias.
|
||||
"""
|
||||
normalized = _GROOT_MODEL_VERSION_ALIASES.get(model_version.lower())
|
||||
if normalized is None:
|
||||
supported = GROOT_N1_7
|
||||
@@ -90,11 +85,6 @@ def normalize_groot_model_version(model_version: str) -> str:
|
||||
|
||||
|
||||
def normalize_groot_action_decode_transform(transform: str | None) -> str | None:
|
||||
"""Resolve `transform` to a canonical action-decode-transform name, or `None`.
|
||||
|
||||
Raises:
|
||||
ValueError: If `transform` isn't a recognized alias.
|
||||
"""
|
||||
if transform is None:
|
||||
return None
|
||||
normalized = _GROOT_ACTION_DECODE_TRANSFORM_ALIASES.get(transform.lower())
|
||||
@@ -110,7 +100,6 @@ def normalize_groot_action_decode_transform(transform: str | None) -> str | None
|
||||
|
||||
|
||||
def infer_groot_model_version(model_path: str | None) -> str | None:
|
||||
"""Infer the GR00T model version (`GROOT_N1_7`) from a checkpoint path, or `None` if undetermined."""
|
||||
if not model_path:
|
||||
return None
|
||||
model_path_lower = model_path.lower()
|
||||
@@ -128,7 +117,6 @@ def infer_groot_model_version(model_path: str | None) -> str | None:
|
||||
|
||||
|
||||
def is_raw_groot_n1_7_checkpoint(model_path: str | Path | None) -> bool:
|
||||
"""Return `True` if `model_path` looks like an un-migrated, raw upstream GR00T N1.7 checkpoint."""
|
||||
if model_path is None:
|
||||
return False
|
||||
|
||||
@@ -145,7 +133,6 @@ def is_raw_groot_n1_7_checkpoint(model_path: str | Path | None) -> bool:
|
||||
|
||||
|
||||
def infer_groot_n1_7_embodiment_tag(model_path: str | Path | None) -> str | None:
|
||||
"""Infer the embodiment tag from a raw GR00T N1.7 checkpoint's `processor_config.json`, if resolvable."""
|
||||
if model_path is None:
|
||||
return None
|
||||
|
||||
@@ -165,13 +152,6 @@ def infer_groot_n1_7_embodiment_tag(model_path: str | Path | None) -> str | None
|
||||
def infer_groot_n1_7_action_horizon(
|
||||
model_path: str | Path | None, embodiment_tag: str | None = None
|
||||
) -> int | None:
|
||||
"""Infer the action horizon from a raw GR00T N1.7 checkpoint's `processor_config.json`, if resolvable.
|
||||
|
||||
Args:
|
||||
model_path (`str | pathlib.Path | None`): Path to the checkpoint directory.
|
||||
embodiment_tag (`str | None`, *optional*): The embodiment tag to look up. Inferred via
|
||||
`infer_groot_n1_7_embodiment_tag` when `None`.
|
||||
"""
|
||||
if model_path is None:
|
||||
return None
|
||||
|
||||
@@ -205,13 +185,6 @@ def infer_groot_n1_7_action_horizon(
|
||||
def infer_groot_n1_7_action_execution_horizon(
|
||||
model_path: str | Path | None, embodiment_tag: str | None = None
|
||||
) -> int | None:
|
||||
"""Infer the action execution horizon (<= action horizon) for a raw GR00T N1.7 checkpoint.
|
||||
|
||||
Args:
|
||||
model_path (`str | pathlib.Path | None`): Path to the checkpoint directory.
|
||||
embodiment_tag (`str | None`, *optional*): The embodiment tag to look up. Inferred via
|
||||
`infer_groot_n1_7_embodiment_tag` when `None`.
|
||||
"""
|
||||
action_horizon = infer_groot_n1_7_action_horizon(model_path, embodiment_tag)
|
||||
if action_horizon is None:
|
||||
return None
|
||||
@@ -268,127 +241,7 @@ def _infer_groot_model_version_from_config(config: dict) -> str | None:
|
||||
@PreTrainedConfig.register_subclass("groot")
|
||||
@dataclass
|
||||
class GrootConfig(PreTrainedConfig):
|
||||
"""Configuration for the GR00T N1.7 policy wrapper.
|
||||
|
||||
Wraps NVIDIA's Isaac-GR00T N1.7 model (a Qwen3-VL/Cosmos-Reason2 backbone plus a flow-matching
|
||||
action head) for fine-tuning and inference through LeRobot. GR00T N1.5 checkpoints and configs are
|
||||
no longer supported; loading one raises with `GROOT_N1_5_REMOVAL_GUIDANCE`.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1): Number of environment steps of observation to
|
||||
pass to the policy (the current step plus this many additional steps looking back).
|
||||
input_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from input feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from output feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*): Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. If unset or unavailable, auto-selected on construction.
|
||||
use_amp (`bool`, *optional*, defaults to `False`): Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`): Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`): Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*): Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*): Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*): Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*): License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`pathlib.Path | None`, *optional*): Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*): Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 40): The size of the action prediction chunk decoded
|
||||
per call to `predict_action_chunk`.
|
||||
n_action_steps (`int`, *optional*, defaults to 40): The number of actions from a predicted
|
||||
chunk that are actually queued for execution. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 132): Maximum observation-state dimension expected
|
||||
by the pretrained GR00T model; shorter states are zero-padded.
|
||||
max_action_dim (`int`, *optional*, defaults to 132): Maximum action dimension expected by the
|
||||
pretrained GR00T model; shorter actions are zero-padded.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*): Per-feature-type
|
||||
normalization mode. Always `IDENTITY` for every feature: GR00T normalizes state/action
|
||||
internally in its own processor steps and the Qwen3-VL image processor handles image
|
||||
normalization, so this mapping is not consulted by `make_groot_pre_post_processors`.
|
||||
base_model_path (`str | None`, *optional*): Path or Hub id of the base GR00T N1.7 model whose
|
||||
backbone weights and checkpoint sidecars (`statistics.json`, `processor_config.json`, ...)
|
||||
are loaded. Distinct from the inherited `pretrained_path`, which points at a saved LeRobot
|
||||
checkpoint directory. Defaults to `GROOT_N1_7_BASE_MODEL` when left unset.
|
||||
action_decode_transform (`str | None`, *optional*, defaults to `"auto"`): Named action transform
|
||||
applied after raw N1.7 checkpoint decoding and before `env.step()`. `"auto"` resolves to the
|
||||
embodiment default (`"libero"` for the `libero_sim` embodiment, otherwise no transform);
|
||||
pass `"none"` to explicitly disable it.
|
||||
embodiment_tag (`str`, *optional*, defaults to `"new_embodiment"`): Embodiment tag to use for
|
||||
training, e.g. `"new_embodiment"` or `"gr1"`.
|
||||
tune_llm (`bool`, *optional*, defaults to `False`): Whether to fine-tune the LLM backbone.
|
||||
tune_visual (`bool`, *optional*, defaults to `False`): Whether to fine-tune the vision tower.
|
||||
tune_projector (`bool`, *optional*, defaults to `True`): Whether to fine-tune the projector.
|
||||
tune_diffusion_model (`bool`, *optional*, defaults to `True`): Whether to fine-tune the
|
||||
flow-matching action head.
|
||||
tune_vlln (`bool`, *optional*, defaults to `True`): Whether to fine-tune the VL LayerNorm and VL
|
||||
self-attention projector in the action head.
|
||||
tune_top_llm_layers (`int`, *optional*, defaults to 0): Number of top LLM backbone layers to
|
||||
fine-tune (0 means none). Lets you adapt just the final language layers without unfreezing
|
||||
the whole backbone; independent of `tune_llm`, which tunes the entire LLM.
|
||||
num_inference_timesteps (`int | None`, *optional*): Number of flow-matching denoising steps used
|
||||
to decode an action chunk at inference time. `None` keeps the checkpoint value (GR00T N1.7
|
||||
default: 4).
|
||||
rtc_ramp_rate (`float | None`, *optional*): Real-Time Chunking overlap-blend ramp rate, used
|
||||
when the RTC engine supplies a previous-chunk prefix. `None` keeps the checkpoint value
|
||||
(GR00T N1.7 default: 6.0).
|
||||
use_flash_attention (`bool`, *optional*, defaults to `False`): Whether to request the
|
||||
flash-attention-2 kernel for the Qwen3-VL backbone. Set to `True` only after installing a
|
||||
flash-attn build matching your torch/CUDA environment; otherwise the backbone falls back to
|
||||
SDPA, which is numerically equivalent.
|
||||
use_relative_actions (`bool`, *optional*, defaults to `False`): Whether to enable GR00T-style
|
||||
state-relative action chunks (the action chunk is expressed relative to the current
|
||||
observation state).
|
||||
relative_exclude_joints (`list[str]`, *optional*): Action dimensions that stay absolute when
|
||||
`use_relative_actions` is set; matched as a case-insensitive substring against the dataset's
|
||||
action feature names. With the empty default every dimension is treated as relative,
|
||||
including the gripper; set e.g. `["gripper"]` to keep the gripper absolute.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001): Learning rate for the AdamW optimizer.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.999)`): AdamW betas, as
|
||||
used by the Isaac-GR00T N1.7 fine-tuning recipe.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08): AdamW epsilon.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-05): AdamW weight decay.
|
||||
warmup_ratio (`float`, *optional*, defaults to 0.05): Fraction of `max_steps` used as cosine
|
||||
scheduler warmup.
|
||||
use_bf16 (`bool`, *optional*, defaults to `True`): Whether to run the GR00T forward/inference
|
||||
passes under BF16 autocast.
|
||||
model_params_fp32 (`bool`, *optional*, defaults to `True`): Whether to keep model parameters in
|
||||
FP32 while computing under BF16 autocast, matching the native N1.7 fine-tuning recipe.
|
||||
image_size (`tuple[int, int]`, *optional*, defaults to `(256, 256)`): Legacy field kept only so
|
||||
that a GR00T N1.5-era `image_size=(224, 224)` config is detected and remapped to the N1.7
|
||||
default in `__post_init__`; image sizing is otherwise handled by the backbone's image
|
||||
processor.
|
||||
tokenizer_assets_repo (`str | None`, *optional*): Deprecated GR00T N1.5 field. Must stay `None`;
|
||||
a non-`None` value is treated as an N1.5 checkpoint/config and rejected in `__post_init__`.
|
||||
lora_rank (`int`, *optional*, defaults to 0): Deprecated, never-wired LoRA field kept only so
|
||||
older saved configs still parse.
|
||||
lora_alpha (`int`, *optional*, defaults to 16): Deprecated, never-wired LoRA field kept only so
|
||||
older saved configs still parse.
|
||||
lora_dropout (`float`, *optional*, defaults to 0.1): Deprecated, never-wired LoRA field kept only
|
||||
so older saved configs still parse.
|
||||
lora_full_model (`bool`, *optional*, defaults to `False`): Deprecated, never-wired LoRA field
|
||||
kept only so older saved configs still parse.
|
||||
video_backend (`str`, *optional*, defaults to `"decord"`): Deprecated Isaac-GR00T runner field;
|
||||
unused by the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
balance_dataset_weights (`bool`, *optional*, defaults to `True`): Deprecated Isaac-GR00T runner
|
||||
field; unused by the LeRobot N1.7 implementation, kept only so older saved configs still
|
||||
parse.
|
||||
balance_trajectory_weights (`bool`, *optional*, defaults to `True`): Deprecated Isaac-GR00T
|
||||
runner field; unused by the LeRobot N1.7 implementation, kept only so older saved configs
|
||||
still parse.
|
||||
dataset_paths (`list[str] | None`, *optional*): Deprecated Isaac-GR00T runner field; unused by
|
||||
the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
output_dir (`str`, *optional*, defaults to `"./tmp/gr00t"`): Deprecated Isaac-GR00T runner field;
|
||||
unused by the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
save_steps (`int`, *optional*, defaults to 1000): Deprecated Isaac-GR00T runner field; unused by
|
||||
the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
max_steps (`int`, *optional*, defaults to 10000): Total training steps; used together with
|
||||
`warmup_ratio` to derive the cosine scheduler's warmup step count in
|
||||
`get_scheduler_preset`.
|
||||
batch_size (`int`, *optional*, defaults to 32): Deprecated Isaac-GR00T runner field; unused by
|
||||
the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
dataloader_num_workers (`int`, *optional*, defaults to 8): Deprecated Isaac-GR00T runner field;
|
||||
unused by the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
report_to (`str`, *optional*, defaults to `"wandb"`): Deprecated Isaac-GR00T runner field; unused
|
||||
by the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
resume (`bool`, *optional*, defaults to `False`): Deprecated Isaac-GR00T runner field; unused by
|
||||
the LeRobot N1.7 implementation, kept only so older saved configs still parse.
|
||||
"""
|
||||
"""Configuration for Groot policy wrapper."""
|
||||
|
||||
# Basic policy settings
|
||||
n_obs_steps: int = 1
|
||||
@@ -519,12 +372,6 @@ class GrootConfig(PreTrainedConfig):
|
||||
resume: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
"""Reject legacy GR00T N1.5 configs, normalize fields, and remap N1.5-era defaults.
|
||||
|
||||
Raises:
|
||||
ValueError: If `tokenizer_assets_repo` is set (an N1.5-only field), if `base_model_path`
|
||||
resolves to a GR00T N1.5 checkpoint, or if `n_action_steps` exceeds `chunk_size`.
|
||||
"""
|
||||
if self.tokenizer_assets_repo is not None:
|
||||
raise ValueError(
|
||||
"Config sets 'tokenizer_assets_repo', which only existed for GR00T N1.5; this looks "
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Groot Policy Wrapper for LeRobot Integration
|
||||
"""
|
||||
Groot Policy Wrapper for LeRobot Integration
|
||||
|
||||
Minimal integration that delegates to Isaac-GR00T N1.7 components where
|
||||
possible without porting their code. Dataset loading and training
|
||||
@@ -68,17 +69,10 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
config_class = GrootConfig
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`]. GR00T N1.7 implements RTC."""
|
||||
return True
|
||||
|
||||
def __init__(self, config: GrootConfig, **kwargs):
|
||||
"""Build the underlying GR00T N1.7 model from `config` and reset the action queue.
|
||||
|
||||
Args:
|
||||
config (GrootConfig): Policy configuration; also validated/completed via
|
||||
`config.validate_features()`.
|
||||
kwargs: Unused; accepted for interface compatibility with `PreTrainedPolicy`.
|
||||
"""
|
||||
"""Initialize Groot policy wrapper."""
|
||||
require_package("transformers", extra="groot")
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -155,7 +149,7 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
]
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the action queue."""
|
||||
"""Reset policy state when environment resets."""
|
||||
self._action_queue = deque([], maxlen=self._action_queue_steps)
|
||||
|
||||
@classmethod
|
||||
@@ -174,40 +168,27 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
strict: bool = True,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""Load a Groot policy from either a raw N1.7 checkpoint or a fine-tuned LeRobot checkpoint.
|
||||
"""Load Groot policy from pretrained model.
|
||||
|
||||
Handles two cases:
|
||||
1. Base GR00T N1.7 models - loads the raw model
|
||||
2. Fine-tuned LeRobot checkpoints - loads config and weights from safetensors
|
||||
|
||||
Args:
|
||||
pretrained_name_or_path (str | Path): Hub id or local path to the GR00T model or the
|
||||
fine-tuned checkpoint.
|
||||
config (GrootConfig | None, *optional*): Config to use. If `None`, one is loaded from the
|
||||
checkpoint (fine-tuned case) or created with defaults (base-model case).
|
||||
force_download (bool, *optional*, defaults to `False`): Whether to force (re-)downloading
|
||||
the files, overriding the existing cache.
|
||||
resume_download (bool | None, *optional*): Deprecated; ignored by the underlying Hub client.
|
||||
proxies (dict | None, *optional*): A dictionary of proxy servers to use by protocol or
|
||||
endpoint.
|
||||
token (str | bool | None, *optional*): The token to use as HTTP bearer authorization for
|
||||
remote files.
|
||||
cache_dir (str | Path | None, *optional*): Path to the folder where cached files are stored.
|
||||
local_files_only (bool, *optional*, defaults to `False`): If `True`, avoid downloading the
|
||||
file and use the local cache only.
|
||||
revision (str | None, *optional*): Revision on the Hub: a branch name, git tag, or commit id.
|
||||
strict (bool, *optional*, defaults to `True`): Whether to require an exact match between the
|
||||
checkpoint's and the instantiated model's parameter keys.
|
||||
kwargs: For the fine-tuned-checkpoint case, forwarded to
|
||||
[`~policies.pretrained.PreTrainedPolicy.from_pretrained`]. For the base-model case,
|
||||
applied as config field overrides.
|
||||
pretrained_name_or_path: Path to the GR00T model or fine-tuned checkpoint
|
||||
config: Optional GrootConfig. If None, loads from checkpoint or creates default
|
||||
force_download: Force download even if cached
|
||||
resume_download: Resume interrupted download
|
||||
proxies: Proxy settings
|
||||
token: HuggingFace authentication token
|
||||
cache_dir: Cache directory path
|
||||
local_files_only: Only use local files
|
||||
revision: Specific model revision
|
||||
strict: Strict state dict loading
|
||||
**kwargs: Additional arguments (passed to config)
|
||||
|
||||
Returns:
|
||||
T: The loaded `GrootPolicy` instance, in eval mode.
|
||||
|
||||
Raises:
|
||||
ValueError: If `config.base_model_path` (or `pretrained_name_or_path`) resolves to an
|
||||
unsupported GR00T model version.
|
||||
Initialized GrootPolicy instance with loaded model
|
||||
"""
|
||||
requested_version = infer_groot_model_version(str(pretrained_name_or_path)) or GROOT_N1_7
|
||||
logger.info(
|
||||
@@ -304,11 +285,7 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
return policy
|
||||
|
||||
def get_optim_params(self): # type: ignore[override]
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Splits parameters into weight-decay and no-weight-decay groups, matching the Isaac-GR00T
|
||||
recipe of excluding biases and normalization parameters from weight decay.
|
||||
"""
|
||||
"""Isaac-GR00T excludes biases and normalization parameters from weight decay."""
|
||||
return self._build_weight_decay_parameter_groups(self)
|
||||
|
||||
def _resolve_action_queue_steps(self) -> int:
|
||||
@@ -330,6 +307,7 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
|
||||
def _resolve_prediction_horizon(self, actions: Tensor) -> int:
|
||||
"""Return the policy-facing action horizon for a native GR00T prediction."""
|
||||
|
||||
horizons = [actions.shape[1]]
|
||||
checkpoint_action_horizon = infer_groot_n1_7_action_horizon(
|
||||
self.config.base_model_path,
|
||||
@@ -466,10 +444,9 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
return inputs, options
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
"""Training forward pass.
|
||||
|
||||
Delegates to the underlying Isaac-GR00T model's `forward`, run under BF16 autocast when
|
||||
`config.use_bf16` is set.
|
||||
Delegates to Isaac-GR00T model.forward when inputs are compatible.
|
||||
"""
|
||||
groot_inputs = self._filter_groot_inputs(batch, include_action=True)
|
||||
|
||||
@@ -495,11 +472,12 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs: object) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
"""Predict a chunk of actions for inference by delegating to Isaac-GR00T.
|
||||
|
||||
Delegates to the underlying Isaac-GR00T model's `get_action`, returning a tensor of shape
|
||||
`(B, n_action_steps, action_dim)`. LeRobot's RTC leftovers, if any, are converted into the
|
||||
native GR00T action-overlap options before calling the model.
|
||||
Returns a tensor of shape (B, n_action_steps, action_dim).
|
||||
|
||||
For N1.7, LeRobot's RTC leftovers are converted into the native GR00T
|
||||
action-overlap options before calling the underlying model.
|
||||
"""
|
||||
self.eval()
|
||||
|
||||
@@ -535,15 +513,7 @@ class GrootPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`.
|
||||
|
||||
Raises:
|
||||
NotImplementedError: If `config.use_relative_actions` is set, since cached relative-chunk
|
||||
actions can be decoded against newer observation states; use `predict_action_chunk`
|
||||
directly instead.
|
||||
"""
|
||||
"""Select single action from action queue."""
|
||||
if getattr(self.config, "use_relative_actions", False):
|
||||
raise NotImplementedError(
|
||||
"GrootPolicy.select_action does not support relative-action policies because cached "
|
||||
|
||||
@@ -165,6 +165,7 @@ def _load_n1_7_checkpoint_processor_assets(config: GrootConfig) -> _GrootN17Chec
|
||||
Returns ``None`` for non-raw N1.7 checkpoints so the generic GR00T pipeline
|
||||
can keep using caller-provided dataset stats and config values.
|
||||
"""
|
||||
|
||||
if not is_raw_groot_n1_7_checkpoint(config.base_model_path):
|
||||
return None
|
||||
|
||||
@@ -272,6 +273,7 @@ def _load_n1_7_checkpoint_stats(
|
||||
joints. LeRobot normalizers operate over a single vector, so this function
|
||||
preserves checkpoint group order while flattening each selected statistic.
|
||||
"""
|
||||
|
||||
if raw_stats is None:
|
||||
all_stats = read_json(checkpoint_path / "statistics.json")
|
||||
raw_stats = all_stats.get(embodiment_tag)
|
||||
@@ -379,6 +381,7 @@ _GROOT_ABSENT_STANDARD_OVERRIDE_KEYS = frozenset(
|
||||
|
||||
def _drop_groot_absent_standard_overrides(overrides: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
"""Strip standard override keys that a GR00T pipeline has no step for."""
|
||||
|
||||
if not overrides:
|
||||
return overrides
|
||||
|
||||
@@ -411,6 +414,7 @@ def _apply_groot_step_overrides(
|
||||
silently (standard normalization keys GR00T has no step for are removed
|
||||
beforehand by ``_drop_groot_absent_standard_overrides``).
|
||||
"""
|
||||
|
||||
if not overrides:
|
||||
return
|
||||
|
||||
@@ -483,6 +487,7 @@ def make_groot_pre_post_processors_from_pretrained(
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Load Groot processors for a raw N1.7 checkpoint or a serialized LeRobot pipeline."""
|
||||
|
||||
# Drop the standard normalizer/unnormalizer override keys lerobot-train emits unconditionally:
|
||||
# GR00T has no such steps, so they would make both the raw-checkpoint and serialized override
|
||||
# paths raise. This must happen before either branch below.
|
||||
@@ -579,6 +584,7 @@ def _reconnect_groot_n1_7_pack_decode_steps(
|
||||
The pack step holds the per-instance raw-state cache that relative-action
|
||||
decoding reads its reference state from; the link itself is not serialized.
|
||||
"""
|
||||
|
||||
pack_step = next(
|
||||
(step for step in preprocessor.steps if isinstance(step, GrootN17PackInputsStep)),
|
||||
None,
|
||||
@@ -1149,13 +1155,13 @@ def make_groot_pre_post_processors(
|
||||
This mirrors SO100-style preprocessing and keeps scales consistent with GR00T.
|
||||
|
||||
Args:
|
||||
config (`GrootConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
dataset_meta (`typing.Any | None`, *optional*): Dataset metadata, forwarded to factories that need more than just `dataset_stats`.
|
||||
config: Groot configuration containing data_config, embodiment_tag, etc.
|
||||
dataset_stats: Optional per-key min/max statistics for normalization before padding.
|
||||
|
||||
Returns:
|
||||
Tuple of (preprocessor, postprocessor) pipelines
|
||||
"""
|
||||
|
||||
dataset_meta = dataset_meta or getattr(config, "_runtime_dataset_meta", None)
|
||||
checkpoint_assets = _load_n1_7_checkpoint_processor_assets(config)
|
||||
checkpoint_stats = checkpoint_assets.stats if checkpoint_assets is not None else None
|
||||
@@ -1348,6 +1354,7 @@ def _to_uint8_np_bthwc(img_t: torch.Tensor) -> np.ndarray:
|
||||
|
||||
def _align_video_horizon(video: np.ndarray, horizon: int | None) -> np.ndarray:
|
||||
"""Match the checkpoint video horizon by truncating or left-padding frames."""
|
||||
|
||||
if horizon is None or horizon <= 0:
|
||||
return video
|
||||
current = video.shape[1]
|
||||
@@ -2003,6 +2010,7 @@ class GrootN17PackInputsStep(ProcessorStep):
|
||||
|
||||
def get_cached_raw_state(self) -> dict[str, np.ndarray] | None:
|
||||
"""Return the latest unnormalized state split by checkpoint modality key."""
|
||||
|
||||
return self._last_raw_state
|
||||
|
||||
def state_dict(self) -> dict[str, torch.Tensor]:
|
||||
@@ -2217,6 +2225,7 @@ def _n1_7_decode_stats_for_action(
|
||||
use_percentiles: bool,
|
||||
) -> tuple[np.ndarray, np.ndarray]:
|
||||
"""Select the min/max arrays needed to decode one checkpoint action group."""
|
||||
|
||||
is_relative = use_relative_action and config_value(action_config.get("rep")) == "relative"
|
||||
modality = "relative_action" if is_relative else "action"
|
||||
stats = raw_stats.get(modality, {}).get(key, {})
|
||||
@@ -2515,7 +2524,8 @@ class GrootActionUnpackUnnormalizeStep(ProcessorStep):
|
||||
return features
|
||||
|
||||
def get_config(self) -> dict[str, Any]:
|
||||
"""Returns a serializable dictionary of the processor's configuration.
|
||||
"""
|
||||
Returns a serializable dictionary of the processor's configuration.
|
||||
|
||||
Excludes 'stats' since they are saved separately via state_dict().
|
||||
"""
|
||||
@@ -2528,7 +2538,8 @@ class GrootActionUnpackUnnormalizeStep(ProcessorStep):
|
||||
}
|
||||
|
||||
def state_dict(self) -> dict[str, torch.Tensor]:
|
||||
"""Returns normalization statistics as a flat state dictionary.
|
||||
"""
|
||||
Returns normalization statistics as a flat state dictionary.
|
||||
|
||||
This enables saving stats to safetensors files, similar to normalizer_processor.
|
||||
"""
|
||||
@@ -2543,7 +2554,8 @@ class GrootActionUnpackUnnormalizeStep(ProcessorStep):
|
||||
return flat
|
||||
|
||||
def load_state_dict(self, state: dict[str, torch.Tensor]) -> None:
|
||||
"""Loads normalization statistics from a flat state dictionary.
|
||||
"""
|
||||
Loads normalization statistics from a flat state dictionary.
|
||||
|
||||
This enables loading stats from safetensors files during from_pretrained.
|
||||
"""
|
||||
|
||||
@@ -35,103 +35,7 @@ from lerobot.utils.constants import ACTION
|
||||
@PreTrainedConfig.register_subclass("lingbot_va")
|
||||
@dataclass
|
||||
class LingBotVAConfig(PreTrainedConfig):
|
||||
"""Configuration for the native LingBot-VA policy integration in LeRobot.
|
||||
|
||||
Defaults match the upstream LIBERO configuration (`wan_va/configs/va_libero_cfg.py`) and the
|
||||
`transformer/config.json` of the released checkpoints.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1): Number of environment steps of observation to
|
||||
pass to the policy (the current step plus this many additional steps looking back).
|
||||
input_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from input feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from output feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*): Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. If unset or unavailable, auto-selected on construction.
|
||||
use_amp (`bool`, *optional*, defaults to `False`): Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`): Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`): Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*): Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*): Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*): Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*): License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`pathlib.Path | None`, *optional*): Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*): Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
patch_size (`tuple[int, int, int]`, *optional*, defaults to `(1, 2, 2)`): Wan transformer's
|
||||
spatiotemporal patch size (time, height, width).
|
||||
num_attention_heads (`int`, *optional*, defaults to 24): Number of attention heads in the Wan
|
||||
transformer.
|
||||
attention_head_dim (`int`, *optional*, defaults to 128): Dimension per attention head.
|
||||
in_channels (`int`, *optional*, defaults to 48): Number of input channels to the transformer
|
||||
(VAE latent channels).
|
||||
out_channels (`int`, *optional*, defaults to 48): Number of output channels from the
|
||||
transformer.
|
||||
action_dim (`int`, *optional*, defaults to 30): Dimension of the action stream fed to and
|
||||
predicted by the transformer.
|
||||
text_dim (`int`, *optional*, defaults to 4096): Dimension of the UMT5 text embeddings.
|
||||
freq_dim (`int`, *optional*, defaults to 256): Dimension of the sinusoidal timestep embedding.
|
||||
ffn_dim (`int`, *optional*, defaults to 14336): Hidden dimension of the transformer's
|
||||
feed-forward blocks.
|
||||
num_layers (`int`, *optional*, defaults to 30): Number of transformer layers.
|
||||
cross_attn_norm (`bool`, *optional*, defaults to `True`): Whether to normalize the
|
||||
cross-attention inputs.
|
||||
eps (`float`, *optional*, defaults to 1e-06): Epsilon used in the transformer's normalization
|
||||
layers.
|
||||
rope_max_seq_len (`int`, *optional*, defaults to 1024): Maximum sequence length for the
|
||||
transformer's rotary position embeddings.
|
||||
attn_mode (`str`, *optional*, defaults to `"torch"`): Attention backend. `"torch"` (SDPA) or
|
||||
`"flashattn"` for inference; `"flex"` for training only, and only on a recent torch.
|
||||
wan_pretrained_path (`str`, *optional*, defaults to `"robbyant/lingbot-va-base"`): Hub id or
|
||||
local directory holding the frozen VAE, UMT5 text encoder, and tokenizer sub-folders
|
||||
(diffusers layout, ~20 GB). Lazily loaded and not bundled in the checkpoint.
|
||||
dtype (`str`, *optional*, defaults to `"bfloat16"`): Transformer/VAE/text-encoder dtype:
|
||||
`"bfloat16"`, `"float16"`, or `"float32"`.
|
||||
text_encoder_device (`str`, *optional*, defaults to `"cpu"`): Device for the frozen UMT5-XXL
|
||||
text encoder, which runs once per episode. `"cpu"` frees ~11 GB of VRAM.
|
||||
obs_cam_keys (`list[str]`, *optional*): Observation camera keys, in concatenation order (order
|
||||
matters: latents are concatenated on width). Defaults to the LIBERO camera keys.
|
||||
image_hflip (`bool`, *optional*, defaults to `False`): Whether to undo the LIBERO env
|
||||
processor's extra horizontal flip, to match the model's training orientation.
|
||||
camera_layout (`str`, *optional*, defaults to `"width_concat"`): Camera latent layout:
|
||||
`"width_concat"` (cameras concatenated on width; LIBERO) or `"robotwin_tshape"` (full-res
|
||||
head plus half-res wrists in a "T"; RoboTwin).
|
||||
height (`int`, *optional*, defaults to 128): Observation image height fed to the VAE.
|
||||
width (`int`, *optional*, defaults to 128): Observation image width fed to the VAE.
|
||||
action_per_frame (`int`, *optional*, defaults to 4): Number of single-step actions decoded per
|
||||
predicted video frame.
|
||||
frame_chunk_size (`int`, *optional*, defaults to 4): Number of video frames predicted per
|
||||
autoregressive chunk.
|
||||
attn_window (`int`, *optional*, defaults to 30): Attention window size, in frames, for the
|
||||
causal streaming KV cache.
|
||||
num_inference_steps (`int`, *optional*, defaults to 20): Number of denoising steps for the
|
||||
video-latent flow-matching scheduler.
|
||||
video_exec_step (`int`, *optional*, defaults to -1): Which decoded video frame index to treat
|
||||
as "executed" for KV-cache feedback. `-1` uses the last frame.
|
||||
action_num_inference_steps (`int`, *optional*, defaults to 50): Number of denoising steps for
|
||||
the action flow-matching scheduler.
|
||||
guidance_scale (`float`, *optional*, defaults to 5.0): Classifier-free guidance scale for the
|
||||
video-latent stream.
|
||||
action_guidance_scale (`float`, *optional*, defaults to 1.0): Classifier-free guidance scale
|
||||
for the action stream.
|
||||
snr_shift (`float`, *optional*, defaults to 5.0): Flow-matching noise-schedule shift for the
|
||||
video-latent stream.
|
||||
action_snr_shift (`float`, *optional*, defaults to 0.05): Flow-matching noise-schedule shift
|
||||
for the action stream.
|
||||
max_sequence_length (`int`, *optional*, defaults to 512): Maximum UMT5 prompt length.
|
||||
used_action_channel_ids (`list[int]`, *optional*): Subset of the 30-d action space used by the
|
||||
benchmark; defaults to the first 7 channels (LIBERO's 7-DoF action). The action
|
||||
(un)normalization quantiles live in the checkpoint's `policy_postprocessor.json`, not here.
|
||||
save_predicted_video (`bool`, *optional*, defaults to `False`): Whether to VAE-decode predicted
|
||||
video latents into `self.last_predicted_frames`, opt-in for saving MP4s.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*): Per-feature-type
|
||||
normalization mode. Always `IDENTITY`: images are scaled and VAE-encoded, and actions are
|
||||
quantile-(un)normalized, inside the policy or a dedicated processor step.
|
||||
optimizer_lr (`float`, *optional*, defaults to 1e-05): AdamW learning rate.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`): AdamW betas.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08): AdamW epsilon.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.0001): AdamW weight decay.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0): Gradient clipping norm.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000): Number of linear-warmup steps
|
||||
before the constant learning-rate phase.
|
||||
"""
|
||||
"""Configuration for the native LingBot-VA policy integration in LeRobot."""
|
||||
|
||||
# Wan transformer architecture
|
||||
patch_size: tuple[int, int, int] = (1, 2, 2)
|
||||
@@ -210,11 +114,6 @@ class LingBotVAConfig(PreTrainedConfig):
|
||||
scheduler_warmup_steps: int = 1000
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate `attn_mode`.
|
||||
|
||||
Raises:
|
||||
ValueError: If `attn_mode` is not one of `"torch"`, `"flashattn"`, or `"flex"`.
|
||||
"""
|
||||
super().__post_init__()
|
||||
if self.attn_mode not in ("torch", "flashattn", "flex"):
|
||||
raise ValueError(f"attn_mode must be one of 'torch', 'flashattn', 'flex'; got {self.attn_mode!r}")
|
||||
@@ -230,11 +129,6 @@ class LingBotVAConfig(PreTrainedConfig):
|
||||
return self.chunk_size
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""Validate and set up input/output features for LingBot-VA.
|
||||
|
||||
Raises:
|
||||
ValueError: If no visual input feature is present in `input_features`.
|
||||
"""
|
||||
image_features = [key for key, feat in self.input_features.items() if feat.type == FeatureType.VISUAL]
|
||||
if not image_features:
|
||||
raise ValueError(
|
||||
@@ -247,7 +141,6 @@ class LingBotVAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""Return the AdamW optimizer configuration built from the `optimizer_*` fields."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -257,23 +150,19 @@ class LingBotVAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> LRSchedulerConfig | None:
|
||||
"""Return the linear-warmup-then-constant scheduler configuration, matching upstream's `warmup_constant_lambda`."""
|
||||
# Upstream uses a linear warmup followed by a constant LR (warmup_constant_lambda).
|
||||
return ConstantWithWarmupSchedulerConfig(num_warmup_steps=self.scheduler_warmup_steps)
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list[int]:
|
||||
"""Return the keyframe-sampling indices used to build the observed-frame history."""
|
||||
temporal_downsample = 4
|
||||
stride = max(1, self.action_per_frame // temporal_downsample)
|
||||
return list(range(0, self.frame_chunk_size * temporal_downsample * stride, stride))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""Return indices for delta actions."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""Return indices for delta rewards (None for LingBot-VA)."""
|
||||
return None
|
||||
|
||||
@@ -66,17 +66,6 @@ class LingBotVAPolicy(PreTrainedPolicy):
|
||||
name = "lingbot_va"
|
||||
|
||||
def __init__(self, config: LingBotVAConfig, **kwargs):
|
||||
"""Build the trainable Wan dual-stream transformer and reset per-episode streaming state.
|
||||
|
||||
The VAE, UMT5 text encoder, and tokenizer are frozen and lazily loaded from
|
||||
`config.wan_pretrained_path` on first use; only the transformer is saved in the LeRobot
|
||||
checkpoint.
|
||||
|
||||
Args:
|
||||
config (LingBotVAConfig): Policy configuration; also validated/completed via
|
||||
`config.validate_features()`.
|
||||
kwargs: Unused; accepted for interface compatibility with `PreTrainedPolicy`.
|
||||
"""
|
||||
require_package("diffusers", extra="lingbot_va")
|
||||
require_package("transformers", extra="lingbot_va")
|
||||
super().__init__(config)
|
||||
@@ -157,18 +146,12 @@ class LingBotVAPolicy(PreTrainedPolicy):
|
||||
|
||||
# PreTrainedPolicy API
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Only the transformer is trainable; the VAE and text encoder stay frozen (kept outside the
|
||||
`nn.Module` registry). With PEFT/LoRA this naturally returns just the adapter params.
|
||||
"""
|
||||
# Only the transformer is trainable; the VAE / text encoder stay frozen (kept outside the
|
||||
# nn.Module registry). With PEFT/LoRA this naturally returns just the adapter params.
|
||||
return [p for p in self.transformer.parameters() if p.requires_grad]
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Resets all per-episode streaming state (KV cache, queues, frame counter).
|
||||
"""
|
||||
"""Reset all per-episode streaming state (KV cache, queues, frame counter)."""
|
||||
cfg = self.config
|
||||
self._action_queue: deque = deque(maxlen=cfg.n_action_steps)
|
||||
self._obs_buffer: list = [] # raw keyframe obs (one per env substep) observed this chunk
|
||||
@@ -340,11 +323,11 @@ class LingBotVAPolicy(PreTrainedPolicy):
|
||||
return loss, {"latent_loss": latent_loss.item(), "action_loss": action_loss.item()}
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
"""Training forward: dual-stream flow-matching loss.
|
||||
|
||||
Builds the (video-latent, action, text) training streams from a LeRobot batch
|
||||
(VAE-encoding the camera frames and UMT5-encoding the task), then runs the flow-matching
|
||||
dual-stream loss. Requires the policy to be built with `attn_mode='flex'`.
|
||||
dual-stream loss. Requires the policy to be built with ``attn_mode='flex'``.
|
||||
"""
|
||||
self._ensure_frozen_modules()
|
||||
latents, actions, actions_mask, text_emb = self._build_training_streams(batch)
|
||||
@@ -418,14 +401,12 @@ class LingBotVAPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
"""Return one action, refilling the chunk (and feeding back observed keyframes) as needed.
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`, refilling it (and feeding back
|
||||
observed keyframes) as needed. Mirrors the upstream LIBERO client loop
|
||||
(`evaluation/libero/client.py`): the first observation is the conditioning frame; every
|
||||
observation produced afterwards is buffered as a keyframe and, once the chunk's actions are
|
||||
exhausted, the buffered frames plus executed actions are fed back into the KV cache before the
|
||||
next chunk is predicted.
|
||||
Mirrors the upstream LIBERO client loop (``evaluation/libero/client.py``): the first obs is
|
||||
the conditioning frame; every observation produced afterwards is buffered as a keyframe and,
|
||||
once the chunk's actions are exhausted, the buffered frames + executed actions are fed back
|
||||
into the KV cache before the next chunk is predicted.
|
||||
"""
|
||||
self.eval()
|
||||
self._ensure_frozen_modules()
|
||||
@@ -456,11 +437,7 @@ class LingBotVAPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Runs one autoregressive chunk and returns actions of shape `[B, chunk_size, n_used]`
|
||||
(normalized).
|
||||
"""
|
||||
"""Run one autoregressive chunk and return actions ``[B, chunk_size, n_used]`` (normalized)."""
|
||||
self.eval()
|
||||
self._ensure_frozen_modules()
|
||||
self._maybe_init_prompt(batch)
|
||||
|
||||
@@ -32,149 +32,7 @@ from ..rtc.configuration_rtc import RTCConfig
|
||||
@PreTrainedConfig.register_subclass("molmoact2")
|
||||
@dataclass
|
||||
class MolmoAct2Config(PreTrainedConfig):
|
||||
"""Configuration for the MolmoAct2 policy, backed by the converted HF checkpoint implementation.
|
||||
|
||||
MolmoAct2 supports three training modes via `action_mode`: `"continuous"` (flow-matching only),
|
||||
`"discrete"` (autoregressive token prediction only), or `"both"` (joint loss). At inference,
|
||||
`inference_action_mode` selects which head generates actions.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1): Number of environment steps of observation to
|
||||
pass to the policy (the current step plus this many additional steps looking back).
|
||||
input_features (`dict[str, PolicyFeature]`, *optional*): Mapping from input feature name to its
|
||||
`PolicyFeature` (type and shape). Left empty to be inferred from the dataset.
|
||||
output_features (`dict[str, PolicyFeature]`, *optional*): Mapping from output feature name
|
||||
(e.g. `"action"`) to its `PolicyFeature`. Left empty to be inferred from the dataset.
|
||||
device (`str | None`, *optional*): Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. If unset or unavailable, auto-selected on construction.
|
||||
use_amp (`bool`, *optional*, defaults to `False`): Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`): Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`): Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*): Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*): Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*): Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*): License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`pathlib.Path | None`, *optional*): Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*): Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
checkpoint_path (`str`, *optional*, defaults to `"allenai/MolmoAct2"`): Hub id or local path of
|
||||
the pretrained MolmoAct2 HF checkpoint to load.
|
||||
checkpoint_revision (`str | None`, *optional*): Hub revision (commit hash, branch, or tag) for
|
||||
`checkpoint_path`.
|
||||
checkpoint_force_download (`bool`, *optional*, defaults to `False`): Whether to force
|
||||
re-downloading the checkpoint files, overriding the existing cache.
|
||||
chunk_size (`int`, *optional*, defaults to 30): The size of the action prediction chunk decoded
|
||||
per call to `predict_action_chunk`.
|
||||
n_action_steps (`int`, *optional*, defaults to 30): The number of actions from a predicted
|
||||
chunk that are actually queued for execution. Must not exceed `chunk_size`.
|
||||
action_mode (`str`, *optional*, defaults to `"both"`): Which action head(s) to train:
|
||||
`"continuous"`, `"discrete"`, or `"both"`.
|
||||
inference_action_mode (`str | None`, *optional*): Which action head to use at inference time,
|
||||
`"continuous"` or `"discrete"`. `None` defers to `action_mode`; must be compatible with it.
|
||||
discrete_action_tokenizer (`str`, *optional*, defaults to `"allenai/MolmoAct2-FAST-Tokenizer"`): Hub
|
||||
id of the FAST tokenizer used for discrete action generation.
|
||||
discrete_generation_max_steps (`int | None`, *optional*): Maximum number of autoregressive
|
||||
decoding steps for discrete action generation. `None` uses the checkpoint-derived default.
|
||||
norm_tag (`str | None`, *optional*): Tag identifying which normalization statistics to load
|
||||
from the checkpoint when `dataset_stats` isn't supplied to the processor factory.
|
||||
setup_type (`str`, *optional*, defaults to `""`): Setup-token identifier injected into the prompt; the empty
|
||||
default falls back to checkpoint metadata.
|
||||
control_mode (`str`, *optional*, defaults to `""`): Control-token identifier injected into the prompt; the empty
|
||||
default falls back to checkpoint metadata.
|
||||
image_keys (`list[str]`, *optional*): Explicit observation image keys to feed the model, in
|
||||
order. Falls back to checkpoint metadata, then to the visual features in `input_features`,
|
||||
when empty.
|
||||
normalize_language (`bool`, *optional*, defaults to `True`): Whether to normalize the language
|
||||
instruction text before tokenization.
|
||||
add_setup_tokens (`bool`, *optional*, defaults to `True`): Whether to inject setup tokens into
|
||||
the prompt.
|
||||
add_control_tokens (`bool`, *optional*, defaults to `True`): Whether to inject control tokens
|
||||
into the prompt.
|
||||
normalize_gripper (`bool`, *optional*, defaults to `False`): Whether to apply a dedicated
|
||||
gripper mask when normalizing/unnormalizing state and action.
|
||||
num_state_tokens (`int`, *optional*, defaults to 256): Number of tokens used to represent the
|
||||
proprioceptive state.
|
||||
max_sequence_length (`int | None`, *optional*): Maximum input sequence length. `None` uses the
|
||||
default MolmoAct2 sequence budget inferred from the fixed image/prompt/state/action token
|
||||
layout; override only for unusually long prompts.
|
||||
expected_max_action_dim (`int`, *optional*, defaults to 32): Action dimension the released
|
||||
MolmoAct2 checkpoints are fixed to; validated against the loaded checkpoint at model load.
|
||||
num_flow_timesteps (`int`, *optional*, defaults to 8): Number of flow-matching timesteps
|
||||
sampled during training.
|
||||
flow_matching_cutoff (`float`, *optional*, defaults to 1.0): Upper cutoff for the sampled
|
||||
flow-matching timestep fraction.
|
||||
flow_matching_time_offset (`float`, *optional*, defaults to 0.001): Offset applied to the
|
||||
sampled flow-matching timestep.
|
||||
flow_matching_time_scale (`float`, *optional*, defaults to 0.999): Scale applied to the sampled
|
||||
flow-matching timestep.
|
||||
flow_matching_beta_alpha (`float`, *optional*, defaults to 1.0): Alpha shape parameter of the
|
||||
Beta distribution used to sample flow-matching timesteps.
|
||||
flow_matching_beta_beta (`float`, *optional*, defaults to 1.5): Beta shape parameter of the Beta
|
||||
distribution used to sample flow-matching timesteps.
|
||||
num_inference_steps (`int | None`, *optional*): Number of flow-matching denoising steps at
|
||||
inference time. `None` keeps the checkpoint default.
|
||||
mask_action_dim_padding (`bool`, *optional*, defaults to `True`): Whether to mask out the
|
||||
zero-padded action dimensions during flow-matching denoising.
|
||||
enable_inference_cuda_graph (`bool`, *optional*, defaults to `True`): Whether to allow the
|
||||
backbone's CUDA graph manager to accelerate inference.
|
||||
per_episode_seed (`bool`, *optional*, defaults to `False`): MolmoAct2-local eval option; when
|
||||
enabled, stochastic continuous action generation uses a rollout-local generator derived
|
||||
from `eval_seed`.
|
||||
eval_seed (`int | None`, *optional*): Seed used to derive the rollout-local generator when
|
||||
`per_episode_seed` is set.
|
||||
rtc_config (`RTCConfig | None`, *optional*): Real-Time Chunking configuration. `None` disables
|
||||
RTC.
|
||||
joint_signs (`list[float] | None`, *optional*): Per-dimension sign correction applied to the
|
||||
observation state before the model and to the predicted action after it, for
|
||||
cross-calibration compatibility. Must be set together with `joint_offsets`.
|
||||
joint_offsets (`list[float] | None`, *optional*): Per-dimension offset correction applied
|
||||
alongside `joint_signs`. Must be set together with `joint_signs` and have the same length.
|
||||
enable_lora_vlm (`bool`, *optional*, defaults to `False`): Whether to apply LoRA adapters to the
|
||||
VLM instead of full fine-tuning.
|
||||
lora_rank (`int`, *optional*, defaults to 64): LoRA rank.
|
||||
lora_alpha (`int`, *optional*, defaults to 16): LoRA alpha.
|
||||
lora_dropout (`float`, *optional*, defaults to 0.05): LoRA dropout probability.
|
||||
lora_bias (`str`, *optional*, defaults to `"none"`): Which biases to train with LoRA:
|
||||
`"none"`, `"all"`, or `"lora_only"`.
|
||||
enable_lora_action_expert (`bool`, *optional*, defaults to `False`): Whether to also apply LoRA
|
||||
to the action expert. Requires `enable_lora_vlm`.
|
||||
enable_knowledge_insulation (`bool`, *optional*, defaults to `False`): Whether to stop the
|
||||
action expert's gradients from flowing back into the VLM.
|
||||
freeze_embedding (`bool`, *optional*, defaults to `True`): Whether to freeze the input
|
||||
embeddings during training.
|
||||
train_action_expert_only (`bool`, *optional*, defaults to `False`): Whether to train only the
|
||||
action expert parameters. Requires `action_mode="continuous"` and is incompatible with
|
||||
`enable_lora_vlm`.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`): Whether to enable gradient
|
||||
checkpointing on the backbone.
|
||||
model_dtype (`str`, *optional*, defaults to `"bfloat16"`): Torch dtype to load the checkpoint
|
||||
in: `"float32"`, `"bfloat16"`, or `"float16"`.
|
||||
softmax_auxiliary_loss (`bool`, *optional*, defaults to `True`): Whether to add the softmax
|
||||
z-loss auxiliary term to the discrete-token loss.
|
||||
softmax_auxiliary_loss_scale (`float`, *optional*, defaults to 0.0001): Scale of the softmax
|
||||
auxiliary z-loss term.
|
||||
discrete_loss_token_weighting (`str`, *optional*, defaults to `"root_subsegments_root_tokens"`): How
|
||||
to weight tokens in the discrete cross-entropy loss.
|
||||
optimizer_lr (`float`, *optional*, defaults to 1e-05): Base AdamW learning rate.
|
||||
optimizer_vit_lr (`float`, *optional*, defaults to 5e-06): AdamW learning rate for the vision
|
||||
tower.
|
||||
optimizer_connector_lr (`float`, *optional*, defaults to 5e-06): AdamW learning rate for the
|
||||
vision-language connector.
|
||||
optimizer_action_expert_lr (`float`, *optional*, defaults to 5e-05): AdamW learning rate for the
|
||||
action expert.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`): AdamW betas.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-06): AdamW epsilon.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.0): AdamW weight decay.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0): Gradient clipping norm.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 200): Number of warmup steps for the
|
||||
cosine-decay-with-warmup scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 100000): Number of decay steps for the
|
||||
scheduler.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 1e-06): Final learning rate at the end of
|
||||
the decay schedule.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*): Per-feature-type
|
||||
normalization mode; defaults to `IDENTITY` for vision and `QUANTILES` for state/action.
|
||||
dataset_feature_names (`dict[str, Any]`, *optional*): Per-key feature names populated by
|
||||
`set_dataset_feature_metadata`; not meant to be set directly.
|
||||
"""
|
||||
"""MolmoAct2 policy backed by the converted HF checkpoint implementation."""
|
||||
|
||||
checkpoint_path: str = "allenai/MolmoAct2"
|
||||
checkpoint_revision: str | None = None
|
||||
@@ -273,13 +131,6 @@ class MolmoAct2Config(PreTrainedConfig):
|
||||
dataset_feature_names: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Validate the action-mode, LoRA, and joint-frame-transform field combinations.
|
||||
|
||||
Raises:
|
||||
ValueError: If any of the cross-field constraints on `action_mode`,
|
||||
`inference_action_mode`, `joint_signs`/`joint_offsets`, `lora_*`, or the chunking/
|
||||
sequence-length fields are violated.
|
||||
"""
|
||||
super().__post_init__()
|
||||
if (self.joint_signs is None) != (self.joint_offsets is None):
|
||||
raise ValueError("joint_signs and joint_offsets must both be set or both be None.")
|
||||
@@ -348,21 +199,17 @@ class MolmoAct2Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""Return indices for delta observations (None for MolmoAct2)."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""Return indices for delta actions."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""Return indices for delta rewards (None for MolmoAct2)."""
|
||||
return None
|
||||
|
||||
def get_optimizer_preset(self) -> OptimizerConfig:
|
||||
"""Return the AdamW optimizer configuration built from the `optimizer_*` fields."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -372,7 +219,6 @@ class MolmoAct2Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> LRSchedulerConfig | None:
|
||||
"""Return the cosine-decay-with-warmup scheduler configuration built from the `scheduler_*` fields."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -381,12 +227,6 @@ class MolmoAct2Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def set_dataset_feature_metadata(self, features: dict[str, Any]) -> None:
|
||||
"""Record the dataset's action/state feature names into `dataset_feature_names`.
|
||||
|
||||
Args:
|
||||
features (dict[str, Any]): Dataset feature metadata, keyed by feature name (as found in
|
||||
`LeRobotDatasetMetadata.features`).
|
||||
"""
|
||||
self.dataset_feature_names = {}
|
||||
for key in (ACTION, OBS_STATE):
|
||||
feature = features.get(key) if isinstance(features, dict) else None
|
||||
|
||||
@@ -521,10 +521,6 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
name = "molmoact2"
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`].
|
||||
|
||||
MolmoAct2 implements RTC only for the continuous (flow-matching) action head.
|
||||
"""
|
||||
return self.config.inference_action_mode == "continuous"
|
||||
|
||||
def __init__(
|
||||
@@ -535,16 +531,6 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
dataset_meta: Any | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Load the vendored HF MolmoAct2 model from `config.checkpoint_path` and reset the action queue.
|
||||
|
||||
Args:
|
||||
config (MolmoAct2Config): Policy configuration.
|
||||
inputs: Unused; accepted for interface compatibility with `PreTrainedPolicy`.
|
||||
dataset_stats (dict[str, dict[str, Tensor]] | None, *optional*): Unused by this
|
||||
constructor; normalization statistics are instead supplied to the processor factory.
|
||||
dataset_meta (Any | None, *optional*): Unused by this constructor.
|
||||
kwargs: Unused; accepted for interface compatibility with `PreTrainedPolicy`.
|
||||
"""
|
||||
super().__init__(config, *inputs, **kwargs)
|
||||
_apply_norm_tag_metadata(self.config)
|
||||
self.config.validate_features()
|
||||
@@ -624,10 +610,7 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
self.train(self.training)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Clears the action queue and the rollout-local action generator.
|
||||
"""
|
||||
"""Clear the action queue and rollout generator between episodes."""
|
||||
self._action_queue = deque(maxlen=self.config.n_action_steps)
|
||||
self._rollout_action_generator = None
|
||||
|
||||
@@ -651,7 +634,6 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
set_enabled(enabled)
|
||||
|
||||
def init_rtc_processor(self) -> None:
|
||||
"""(Re)build `self.rtc_processor` from `config.rtc_config`, or clear it when RTC is disabled."""
|
||||
self.rtc_processor = None
|
||||
if self.config.rtc_config is not None:
|
||||
self.rtc_processor = RTCProcessor(self.config.rtc_config)
|
||||
@@ -701,17 +683,6 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
raise RuntimeError("enable_lora_vlm=true, but no action_expert parameters were found.")
|
||||
|
||||
def train(self, mode: bool = True):
|
||||
"""Set training mode, keeping the backbone frozen in eval mode when `train_action_expert_only`.
|
||||
|
||||
Also toggles the inference CUDA graph managers off while training and on while evaluating.
|
||||
|
||||
Args:
|
||||
mode (bool, *optional*, defaults to `True`): Whether to set training (`True`) or
|
||||
evaluation (`False`) mode.
|
||||
|
||||
Returns:
|
||||
MolmoAct2Policy: `self`.
|
||||
"""
|
||||
super().train(mode)
|
||||
if getattr(self.config, "train_action_expert_only", False) and hasattr(self, "model"):
|
||||
self._hf_model().eval()
|
||||
@@ -748,11 +719,7 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
param.requires_grad = False
|
||||
|
||||
def get_optim_params(self) -> list[dict[str, Any]]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Splits parameters into per-component groups (vision tower, connector, action expert, and the
|
||||
rest), each with its own learning rate taken from the corresponding `optimizer_*_lr` field.
|
||||
"""
|
||||
"""Return optimizer param groups with per-component learning rates."""
|
||||
vit_params: list[Tensor] = []
|
||||
connector_params: list[Tensor] = []
|
||||
action_expert_params: list[Tensor] = []
|
||||
@@ -1611,11 +1578,7 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
batch: dict[str, Tensor],
|
||||
reduction: str = "mean",
|
||||
) -> tuple[Tensor, dict[str, Any]]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Computes the flow-matching loss, the discrete cross-entropy loss, or their sum, depending on
|
||||
`config.action_mode`.
|
||||
"""
|
||||
"""Compute training loss (flow-matching and/or discrete token loss)."""
|
||||
if reduction not in {"mean", "none"}:
|
||||
raise ValueError(f"Unsupported reduction={reduction!r}. Expected 'mean' or 'none'.")
|
||||
model_inputs = self._model_inputs(batch)
|
||||
@@ -1675,12 +1638,7 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Generates the chunk via continuous flow matching or discrete autoregressive decoding,
|
||||
depending on the resolved inference action mode; continuous generation additionally supports
|
||||
RTC when `config.rtc_config` is set.
|
||||
"""
|
||||
"""Generate an action chunk via continuous flow matching or discrete AR decoding."""
|
||||
if "action_mode" in kwargs:
|
||||
raise TypeError(
|
||||
"MolmoAct2 predict_action_chunk got unexpected keyword argument 'action_mode'; "
|
||||
@@ -1735,14 +1693,7 @@ class MolmoAct2Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], **kwargs) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`.
|
||||
|
||||
Raises:
|
||||
AssertionError: If RTC is enabled, since RTC is only supported through
|
||||
`predict_action_chunk`.
|
||||
"""
|
||||
"""Pop one action step from the queue, regenerating the chunk when empty."""
|
||||
if self._rtc_enabled():
|
||||
raise AssertionError("RTC is not supported for select_action, use it with predict_action_chunk")
|
||||
self.eval()
|
||||
|
||||
@@ -1116,27 +1116,6 @@ def make_molmoact2_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Build the pre/post-processor pipeline pair for the MolmoAct2 policy.
|
||||
|
||||
The preprocessor renames observation keys, adds a batch dimension, applies the optional
|
||||
joint-frame transform, masked-normalizes state/action with dataset statistics, and packs
|
||||
everything (video, state, action, language, setup/control tokens) into the vendored HF model's
|
||||
input format before moving tensors to `config.device`. The postprocessor reverses the
|
||||
normalization and joint-frame transform on the predicted actions.
|
||||
|
||||
Args:
|
||||
config (MolmoAct2Config): Policy configuration; supplies feature keys, checkpoint-derived
|
||||
metadata, and the normalization mapping.
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None, *optional*): Per-feature statistics
|
||||
used for state/action normalization. If `None` and `config.norm_tag` is set, statistics
|
||||
are instead loaded from the checkpoint's own normalization metadata.
|
||||
dataset_meta (Any | None, *optional*): Dataset metadata, used to build gripper masks for the
|
||||
masked normalizer/unnormalizer steps.
|
||||
|
||||
Returns:
|
||||
`tuple[PolicyProcessorPipeline, PolicyProcessorPipeline]`: The `(preprocessor, postprocessor)`
|
||||
pipeline pair.
|
||||
"""
|
||||
env_action_dim = None
|
||||
if config.output_features and ACTION in config.output_features:
|
||||
env_action_dim = int(config.output_features[ACTION].shape[0])
|
||||
|
||||
@@ -28,141 +28,6 @@ class MultiTaskDiTConfig(PreTrainedConfig):
|
||||
|
||||
A transformer-based policy that supports both diffusion and flow matching objectives
|
||||
for multi-task robot learning with text and vision conditioning.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 2):
|
||||
Number of observation timesteps used for temporal context.
|
||||
input_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Input feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
output_features (`dict[str, PolicyFeature]`, *optional*):
|
||||
Output feature specification, keyed by feature name. Left empty to infer from the dataset.
|
||||
device (`str`, *optional*):
|
||||
Torch device to run the policy on, e.g. `"cuda"` or `"cpu"`. Auto-selected when unset or
|
||||
unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str`, *optional*):
|
||||
Hub repository id to push the policy to.
|
||||
private (`bool`, *optional*):
|
||||
Whether the pushed Hub repository is private.
|
||||
tags (`list[str]`, *optional*):
|
||||
Tags to attach to the policy on the Hub.
|
||||
license (`str`, *optional*):
|
||||
License identifier for the policy on the Hub.
|
||||
pretrained_path (`Path`, *optional*):
|
||||
Repo id or local directory of pretrained weights saved with `save_pretrained`. Left unset to
|
||||
initialize from scratch.
|
||||
pretrained_revision (`str`, *optional*):
|
||||
Hub revision to pin when loading `pretrained_path`.
|
||||
horizon (`int`, *optional*, defaults to 32):
|
||||
Number of action steps predicted per policy call.
|
||||
n_action_steps (`int`, *optional*, defaults to 24):
|
||||
Number of actions from a predicted chunk that are actually executed before re-querying the
|
||||
policy, roughly 0.8s of actions at 30Hz.
|
||||
objective (`str`, *optional*, defaults to `"diffusion"`):
|
||||
Action-generation objective, either `"diffusion"` or `"flow_matching"`.
|
||||
noise_scheduler_type (`str`, *optional*, defaults to `"DDPM"`):
|
||||
Diffusion noise scheduler, either `"DDPM"` or `"DDIM"`. Used when `objective="diffusion"`.
|
||||
num_train_timesteps (`int`, *optional*, defaults to 100):
|
||||
Number of diffusion timesteps used during training. Used when `objective="diffusion"`.
|
||||
beta_schedule (`str`, *optional*, defaults to `"squaredcos_cap_v2"`):
|
||||
Noise schedule type for the diffusion scheduler. Used when `objective="diffusion"`.
|
||||
beta_start (`float`, *optional*, defaults to 0.0001):
|
||||
Starting noise level of the diffusion schedule. Used when `objective="diffusion"`.
|
||||
beta_end (`float`, *optional*, defaults to 0.02):
|
||||
Ending noise level of the diffusion schedule. Used when `objective="diffusion"`.
|
||||
prediction_type (`str`, *optional*, defaults to `"epsilon"`):
|
||||
What the diffusion model predicts: `"epsilon"` for the noise, or `"sample"` for the clean
|
||||
action. Used when `objective="diffusion"`.
|
||||
clip_sample (`bool`, *optional*, defaults to `True`):
|
||||
Whether to clip samples to `clip_sample_range` during denoising. Used when
|
||||
`objective="diffusion"`.
|
||||
clip_sample_range (`float`, *optional*, defaults to 1.0):
|
||||
Clipping range `[-x, x]` applied when `clip_sample` is `True`.
|
||||
num_inference_steps (`int`, *optional*):
|
||||
Number of denoising steps at inference. Defaults to `num_train_timesteps` when left unset.
|
||||
Used when `objective="diffusion"`.
|
||||
sigma_min (`float`, *optional*, defaults to 0.0):
|
||||
Minimum noise level in the flow-matching interpolation path. Used when
|
||||
`objective="flow_matching"`.
|
||||
num_integration_steps (`int`, *optional*, defaults to 100):
|
||||
Number of ODE integration steps at inference. Used when `objective="flow_matching"`.
|
||||
integration_method (`str`, *optional*, defaults to `"euler"`):
|
||||
ODE solver for flow-matching sampling, either `"euler"` or `"rk4"`.
|
||||
timestep_sampling_strategy (`str`, *optional*, defaults to `"beta"`):
|
||||
How training timesteps are sampled for flow matching, either `"uniform"` or `"beta"`.
|
||||
timestep_sampling_s (`float`, *optional*, defaults to 0.999):
|
||||
Maximum timestep threshold, used only when `timestep_sampling_strategy="beta"`.
|
||||
timestep_sampling_alpha (`float`, *optional*, defaults to 1.5):
|
||||
Alpha parameter of the Beta distribution, used only when `timestep_sampling_strategy="beta"`.
|
||||
timestep_sampling_beta (`float`, *optional*, defaults to 1.0):
|
||||
Beta parameter of the Beta distribution, used only when `timestep_sampling_strategy="beta"`.
|
||||
hidden_dim (`int`, *optional*, defaults to 512):
|
||||
Transformer hidden dimension.
|
||||
num_layers (`int`, *optional*, defaults to 6):
|
||||
Number of transformer layers.
|
||||
num_heads (`int`, *optional*, defaults to 8):
|
||||
Number of attention heads. Must divide `hidden_dim`.
|
||||
dropout (`float`, *optional*, defaults to 0.1):
|
||||
Dropout rate applied inside the transformer.
|
||||
use_positional_encoding (`bool`, *optional*, defaults to `False`):
|
||||
Whether to add a learned absolute positional encoding to the action sequence.
|
||||
timestep_embed_dim (`int`, *optional*, defaults to 256):
|
||||
Dimensionality of the diffusion/flow-matching timestep embedding.
|
||||
use_rope (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use Rotary Position Embedding in self-attention instead of standard multi-head
|
||||
attention.
|
||||
rope_base (`float`, *optional*, defaults to 10000.0):
|
||||
Base frequency for Rotary Position Embedding. Used when `use_rope` is `True`.
|
||||
vision_encoder_name (`str`, *optional*, defaults to `"openai/clip-vit-base-patch16"`):
|
||||
Hugging Face Hub id of the CLIP vision model used to encode camera images. Must be a CLIP
|
||||
model.
|
||||
use_separate_rgb_encoder_per_camera (`bool`, *optional*, defaults to `False`):
|
||||
Whether to instantiate one vision encoder per camera view instead of sharing a single one.
|
||||
vision_encoder_lr_multiplier (`float`, *optional*, defaults to 0.1):
|
||||
Learning-rate multiplier applied to the vision encoder's parameter group.
|
||||
image_resize_shape (`tuple[int, int]`, *optional*):
|
||||
Size images are resized to before cropping. `None` skips resizing.
|
||||
image_crop_shape (`tuple[int, int]`, *optional*, defaults to `(224, 224)`):
|
||||
Crop shape applied after resizing. Disabled automatically when it does not fit within the
|
||||
(resized) image.
|
||||
image_crop_is_random (`bool`, *optional*, defaults to `True`):
|
||||
Whether to crop randomly during training. Inference always uses a center crop.
|
||||
text_encoder_name (`str`, *optional*, defaults to `"openai/clip-vit-base-patch16"`):
|
||||
Hugging Face Hub id of the CLIP text model used to encode the language instruction. Must be a
|
||||
CLIP model.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 77):
|
||||
Maximum length for tokenized text.
|
||||
tokenizer_padding (`str`, *optional*, defaults to `"max_length"`):
|
||||
Tokenizer padding strategy, either `"max_length"` or `"longest"`.
|
||||
tokenizer_padding_side (`str`, *optional*, defaults to `"right"`):
|
||||
Tokenizer padding side, either `"left"` or `"right"`.
|
||||
tokenizer_truncation (`bool`, *optional*, defaults to `True`):
|
||||
Whether to truncate sequences longer than `tokenizer_max_length`.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps each `FeatureType` to the `NormalizationMode` used to normalize/unnormalize it.
|
||||
optimizer_lr (`float`, *optional*, defaults to 2e-05):
|
||||
Learning rate used to build the default `AdamConfig` optimizer preset.
|
||||
optimizer_betas (`tuple`, *optional*, defaults to `(0.95, 0.999)`):
|
||||
Adam beta coefficients for the default optimizer preset.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Adam epsilon for the default optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.0):
|
||||
Weight decay for the default optimizer preset.
|
||||
scheduler_name (`str`, *optional*, defaults to `"cosine"`):
|
||||
Name of the learning-rate scheduler preset.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 0):
|
||||
Number of warmup steps for the learning-rate scheduler preset.
|
||||
do_mask_loss_for_padding (`bool`, *optional*, defaults to `False`):
|
||||
Whether to exclude padded action timesteps, marked by `action_is_pad`, from the loss.
|
||||
drop_n_last_frames (`int`, *optional*):
|
||||
Number of trailing frames dropped per episode when building training windows.
|
||||
Auto-computed from `horizon`, `n_action_steps`, and `n_obs_steps` in `__post_init__` when left
|
||||
unset.
|
||||
"""
|
||||
|
||||
n_obs_steps: int = 2 # Number of observation steps for temporal context
|
||||
@@ -240,7 +105,6 @@ class MultiTaskDiTConfig(PreTrainedConfig):
|
||||
drop_n_last_frames: int | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the DiT backbone and diffusion/flow-matching schedule configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
if self.drop_n_last_frames is None:
|
||||
@@ -325,7 +189,6 @@ class MultiTaskDiTConfig(PreTrainedConfig):
|
||||
raise ValueError("timestep_sampling_beta must be positive")
|
||||
|
||||
def get_optimizer_preset(self) -> AdamConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -334,7 +197,6 @@ class MultiTaskDiTConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> DiffuserSchedulerConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return DiffuserSchedulerConfig(
|
||||
name=self.scheduler_name,
|
||||
num_warmup_steps=self.scheduler_warmup_steps,
|
||||
@@ -373,25 +235,20 @@ class MultiTaskDiTConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def is_diffusion(self) -> bool:
|
||||
"""`True` if `objective` is `"diffusion"`."""
|
||||
return self.objective == "diffusion"
|
||||
|
||||
@property
|
||||
def is_flow_matching(self) -> bool:
|
||||
"""`True` if `objective` is `"flow_matching"`."""
|
||||
return self.objective == "flow_matching"
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, 1))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, 1 - self.n_obs_steps + self.horizon))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -68,21 +68,10 @@ from ..utils import populate_queues
|
||||
|
||||
|
||||
class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
"""Multi-Task Diffusion Transformer policy: a DiT that denoises action chunks conditioned on vision,
|
||||
language, and robot state, trained with either a diffusion or a flow-matching objective.
|
||||
"""
|
||||
|
||||
config_class = MultiTaskDiTConfig
|
||||
name = "multi_task_dit"
|
||||
|
||||
def __init__(self, config: MultiTaskDiTConfig, **kwargs):
|
||||
"""Build the observation encoder, the DiT noise/velocity predictor, and the training objective.
|
||||
|
||||
Args:
|
||||
config (`MultiTaskDiTConfig`):
|
||||
Policy configuration. `config.objective` selects between a `DiffusionObjective` and a
|
||||
`FlowMatchingObjective`.
|
||||
"""
|
||||
require_package("transformers", extra="multi_task_dit")
|
||||
require_package("diffusers", extra="multi_task_dit")
|
||||
super().__init__(config)
|
||||
@@ -118,11 +107,7 @@ class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def get_optim_params(self) -> list:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Returns two parameter groups: the vision encoder at `optimizer_lr * vision_encoder_lr_multiplier`,
|
||||
and everything else at the base `optimizer_lr`.
|
||||
"""
|
||||
"""Returns parameter groups with different learning rates for vision vs non-vision parameters"""
|
||||
non_vision_params = []
|
||||
vision_encoder_params = []
|
||||
|
||||
@@ -156,7 +141,7 @@ class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
return actions
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the observation and action queues used by `select_action`."""
|
||||
"""Clear observation and action queues. Should be called on `env.reset()`"""
|
||||
self._queues = {
|
||||
OBS_STATE: deque(maxlen=self.config.n_obs_steps),
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
@@ -167,11 +152,7 @@ class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Samples the chunk via the configured objective's `conditional_sample` (DDPM/DDIM denoising for
|
||||
`objective="diffusion"`, ODE integration for `objective="flow_matching"`).
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations"""
|
||||
self.eval()
|
||||
|
||||
for k in batch:
|
||||
@@ -191,7 +172,7 @@ class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Uses an action queue populated by `predict_action_chunk`."""
|
||||
"""Select a single action given environment observations"""
|
||||
if ACTION in batch:
|
||||
batch = dict(batch) # shallow copy to avoid modifying original
|
||||
batch.pop(ACTION)
|
||||
@@ -208,10 +189,7 @@ class MultiTaskDiTPolicy(PreTrainedPolicy):
|
||||
return action
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Computes the diffusion or flow-matching regression loss, depending on `config.objective`.
|
||||
"""
|
||||
"""Run the batch through the model and compute the loss for training"""
|
||||
batch = self._prepare_batch(batch)
|
||||
|
||||
conditioning_vec = self.observation_encoder.encode(batch)
|
||||
|
||||
@@ -36,7 +36,8 @@ def make_multi_task_dit_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for a Multi-Task DiT policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for a Multi-Task DiT policy.
|
||||
|
||||
The pre-processing pipeline prepares the input data for the model by:
|
||||
1. Renaming features.
|
||||
@@ -50,12 +51,15 @@ def make_multi_task_dit_pre_post_processors(
|
||||
2. Moving the data to the CPU.
|
||||
|
||||
Args:
|
||||
config (`MultiTaskDiTConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the Multi-Task DiT policy,
|
||||
containing feature definitions, normalization mappings, and device information.
|
||||
dataset_stats: A dictionary of statistics used for normalization.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
"""
|
||||
|
||||
steps = make_default_policy_processor_steps(config, dataset_stats, normalizer_device=config.device)
|
||||
|
||||
input_steps = [
|
||||
|
||||
@@ -28,127 +28,6 @@ DEFAULT_IMAGE_SIZE = 224
|
||||
@PreTrainedConfig.register_subclass("pi0")
|
||||
@dataclass
|
||||
class PI0Config(PreTrainedConfig):
|
||||
"""Configuration class for the PI0 flow-matching vision-language-action policy.
|
||||
|
||||
PI0 is a PyTorch port of Physical Intelligence's openpi model: a PaliGemma vision-language backbone
|
||||
paired with a smaller Gemma "action expert" that generates action chunks via flow matching.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
device (`str | None`, *optional*):
|
||||
Device to run the model on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Auto-detected when
|
||||
`None`.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether the policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Repository ID to push the trained policy to on the Hub.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub repository.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to attach to the policy's Hub repository.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Repo ID on the Hub or local directory to load pretrained weights from. The policy is
|
||||
initialized from scratch when `None`.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (commit hash, branch, or tag) to pin the pretrained model version.
|
||||
paligemma_variant (`str`, *optional*, defaults to `"gemma_2b"`):
|
||||
Which PaliGemma backbone variant to use for the vision-language encoder. Must be
|
||||
`"gemma_2b"` or `"gemma_300m"`.
|
||||
action_expert_variant (`str`, *optional*, defaults to `"gemma_300m"`):
|
||||
Which Gemma variant to use for the action expert network. Must be `"gemma_2b"` or
|
||||
`"gemma_300m"`.
|
||||
dtype (`str`, *optional*, defaults to `"float32"`):
|
||||
Model computation dtype. Must be `"bfloat16"` or `"float32"`.
|
||||
chunk_size (`int`, *optional*, defaults to 50):
|
||||
Number of action steps predicted per model invocation (called "action_horizon" in openpi).
|
||||
n_action_steps (`int`, *optional*, defaults to 50):
|
||||
Number of predicted action steps actually executed in the environment before predicting a new
|
||||
chunk. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the observation state vector is zero-padded to when shorter.
|
||||
max_action_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the action vector is zero-padded to when shorter.
|
||||
num_inference_steps (`int`, *optional*, defaults to 10):
|
||||
Number of flow-matching denoising steps performed at inference time.
|
||||
time_sampling_beta_alpha (`float`, *optional*, defaults to 1.5):
|
||||
Alpha shape parameter of the Beta distribution the flow-matching timestep is sampled from
|
||||
during training.
|
||||
time_sampling_beta_beta (`float`, *optional*, defaults to 1.0):
|
||||
Beta shape parameter of the Beta distribution the flow-matching timestep is sampled from
|
||||
during training.
|
||||
time_sampling_scale (`float`, *optional*, defaults to 0.999):
|
||||
Scale applied to the sampled Beta timestep before `time_sampling_offset` is added.
|
||||
time_sampling_offset (`float`, *optional*, defaults to 0.001):
|
||||
Offset added to the scaled Beta timestep sample.
|
||||
min_period (`float`, *optional*, defaults to 0.004):
|
||||
Minimum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
max_period (`float`, *optional*, defaults to 4.0):
|
||||
Maximum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
use_relative_actions (`bool`, *optional*, defaults to `False`):
|
||||
Whether to convert absolute actions to relative (relative to the current state) before feeding
|
||||
them to the model.
|
||||
relative_exclude_joints (`list[str]`, *optional*):
|
||||
Joint names to keep absolute (excluded from the relative conversion) when
|
||||
`use_relative_actions` is enabled. An empty list means every dimension is made relative.
|
||||
action_feature_names (`list[str] | None`, *optional*):
|
||||
Names of the action dimensions, in order. Populated at runtime from dataset metadata by
|
||||
`make_policy`.
|
||||
rtc_config (`RTCConfig | None`, *optional*):
|
||||
Real-Time Chunking configuration. `None` disables RTC inference.
|
||||
image_resolution (`tuple[int, int]`, *optional*, defaults to `(224, 224)`):
|
||||
Target `(height, width)` images are resized (with padding) to before being fed to the vision
|
||||
encoder.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of empty (zero-padded) camera views to add, for models trained with more cameras than
|
||||
are available at inference/training time.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Mapping from feature type (`"VISUAL"`, `"STATE"`, `"ACTION"`) to the `NormalizationMode` used
|
||||
for it.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to enable gradient checkpointing to reduce memory usage during training.
|
||||
compile_model (`bool`, *optional*, defaults to `False`):
|
||||
Whether to compile the model with `torch.compile`.
|
||||
compile_mode (`str`, *optional*, defaults to `"max-autotune"`):
|
||||
The `torch.compile` mode to use when `compile_model` is enabled.
|
||||
freeze_vision_encoder (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the vision encoder's weights during training.
|
||||
train_expert_only (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the entire VLM and train only the action expert and its projections.
|
||||
optimizer_lr (`float`, *optional*, defaults to 2.5e-05):
|
||||
Peak learning rate for the AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`):
|
||||
AdamW `(beta1, beta2)` coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
AdamW epsilon term for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.01):
|
||||
AdamW weight decay coefficient.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0):
|
||||
Maximum gradient norm for clipping.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the learning rate scheduler. Auto-scales down when the total number
|
||||
of training steps is smaller.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Learning rate the scheduler decays to at the end of `scheduler_decay_steps`.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 48):
|
||||
Maximum token length for the language tokenizer.
|
||||
"""
|
||||
|
||||
paligemma_variant: str = "gemma_2b"
|
||||
action_expert_variant: str = "gemma_300m"
|
||||
dtype: str = "float32" # Options: "bfloat16", "float32"
|
||||
@@ -224,7 +103,6 @@ class PI0Config(PreTrainedConfig):
|
||||
tokenizer_max_length: int = 48 # see openpi `__post_init__`
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the PaliGemma backbone configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
# Validate configuration
|
||||
@@ -267,7 +145,6 @@ class PI0Config(PreTrainedConfig):
|
||||
self.output_features[ACTION] = action_feature
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -277,7 +154,6 @@ class PI0Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -287,15 +163,12 @@ class PI0Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -744,17 +744,12 @@ class PI0Pytorch(nn.Module): # see openpi `PI0Pytorch`
|
||||
|
||||
|
||||
class PI0Policy(PreTrainedPolicy):
|
||||
"""PyTorch port of Physical Intelligence's PI0 vision-language-action policy, generating action
|
||||
chunks via flow matching.
|
||||
"""
|
||||
"""PI0 OpenPI Policy for LeRobot."""
|
||||
|
||||
config_class = PI0Config
|
||||
name = "pi0"
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`]. PI0 implements Real-Time Chunking
|
||||
inference.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __init__(
|
||||
@@ -762,10 +757,9 @@ class PI0Policy(PreTrainedPolicy):
|
||||
config: PI0Config,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the underlying PI0 model from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`PI0Config`): Policy configuration class instance.
|
||||
config: Policy configuration class instance.
|
||||
"""
|
||||
require_package("transformers", extra="pi")
|
||||
super().__init__(config)
|
||||
@@ -800,11 +794,7 @@ class PI0Policy(PreTrainedPolicy):
|
||||
strict: bool = True,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`].
|
||||
|
||||
Additionally remaps checkpoint state-dict keys from the upstream openpi naming convention before
|
||||
loading them, and defaults `strict` to `True` rather than `False`.
|
||||
"""
|
||||
"""Override the from_pretrained method to handle key remapping and display important disclaimer."""
|
||||
print(
|
||||
"The PI0 model is a direct port of the OpenPI implementation. \n"
|
||||
"This implementation follows the original OpenPI structure for compatibility. \n"
|
||||
@@ -965,13 +955,10 @@ class PI0Policy(PreTrainedPolicy):
|
||||
return fixed_state_dict
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.parameters()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the cached action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
"""Reset internal state - called when environment resets."""
|
||||
self._action_queue = deque(maxlen=self.config.n_action_steps)
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
@@ -1059,20 +1046,18 @@ class PI0Policy(PreTrainedPolicy):
|
||||
return images, img_masks
|
||||
|
||||
def prepare_state(self, batch):
|
||||
"""Zero-pad the observation state to `config.max_state_dim`."""
|
||||
"""Pad state"""
|
||||
state = pad_vector(batch[OBS_STATE], self.config.max_state_dim)
|
||||
return state
|
||||
|
||||
def prepare_action(self, batch):
|
||||
"""Zero-pad the target action to `config.max_action_dim`."""
|
||||
"""Pad action"""
|
||||
actions = pad_vector(batch[ACTION], self.config.max_action_dim)
|
||||
return actions
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Pops one action off an internal
|
||||
queue, refilling the queue by calling `predict_action_chunk` whenever it is empty.
|
||||
"""
|
||||
"""Select a single action given environment observations."""
|
||||
assert not self._rtc_enabled(), (
|
||||
"RTC is not supported for select_action, use it with predict_action_chunk"
|
||||
)
|
||||
@@ -1089,9 +1074,7 @@ class PI0Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs: Unpack[ActionSelectKwargs]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]. Runs the flow-matching
|
||||
sampler (`config.num_inference_steps` denoising steps) to generate the chunk.
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
self.eval()
|
||||
|
||||
# Prepare inputs
|
||||
@@ -1109,19 +1092,13 @@ class PI0Policy(PreTrainedPolicy):
|
||||
return actions
|
||||
|
||||
def forward(self, batch: dict[str, Tensor], reduction: str = "mean") -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`]. Computes the flow-matching loss between
|
||||
the model's predicted and target velocity fields.
|
||||
"""Run the batch through the model and compute the loss for training.
|
||||
|
||||
Args:
|
||||
batch (`dict[str, Tensor]`):
|
||||
A batch of preprocessed, normalized observation/action tensors, as produced by this
|
||||
policy's preprocessor pipeline.
|
||||
reduction (`str`, *optional*, defaults to `"mean"`):
|
||||
How to reduce the per-element loss. `"mean"` returns a scalar mean loss; `"none"` returns
|
||||
per-sample losses of shape `(batch_size,)`, e.g. for RA-BC weighting.
|
||||
|
||||
Returns:
|
||||
`tuple[Tensor, dict]`: The loss and a dict of logging-friendly loss statistics.
|
||||
batch: Training batch containing observations and actions.
|
||||
reduction: How to reduce the loss. Options:
|
||||
- "mean": Return scalar mean loss (default, backward compatible)
|
||||
- "none": Return per-sample losses of shape (batch_size,) for RA-BC weighting
|
||||
"""
|
||||
# Prepare inputs
|
||||
images, img_masks = self._preprocess_images(batch)
|
||||
|
||||
@@ -37,7 +37,8 @@ from .configuration_pi0 import PI0Config
|
||||
|
||||
@ProcessorStepRegistry.register(name="pi0_new_line_processor")
|
||||
class Pi0NewLineProcessor(ComplementaryDataProcessorStep):
|
||||
"""Ensures that the task description string ends with a newline character.
|
||||
"""
|
||||
Ensures that the task description string ends with a newline character.
|
||||
|
||||
This processing step is required for compatibility with the PaliGemma tokenizer,
|
||||
which expects a newline at the end of the text prompt. It handles both single
|
||||
@@ -45,7 +46,8 @@ class Pi0NewLineProcessor(ComplementaryDataProcessorStep):
|
||||
"""
|
||||
|
||||
def complementary_data(self, complementary_data):
|
||||
"""Adds a newline to the 'task' field if it doesn't already have one.
|
||||
"""
|
||||
Adds a newline to the 'task' field if it doesn't already have one.
|
||||
|
||||
Args:
|
||||
complementary_data: A dictionary that may contain a 'task' key with a
|
||||
@@ -78,7 +80,8 @@ class Pi0NewLineProcessor(ComplementaryDataProcessorStep):
|
||||
def transform_features(
|
||||
self, features: dict[PipelineFeatureType, dict[str, PolicyFeature]]
|
||||
) -> dict[PipelineFeatureType, dict[str, PolicyFeature]]:
|
||||
"""This step does not alter the feature definitions.
|
||||
"""
|
||||
This step does not alter the feature definitions.
|
||||
|
||||
Args:
|
||||
features: The input feature dictionary.
|
||||
@@ -96,7 +99,8 @@ def make_pi0_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the PI0 policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the PI0 policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -111,12 +115,15 @@ def make_pi0_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`PI0Config`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the PI0 policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
preprocessor_kwargs: Additional arguments for the pre-processor pipeline.
|
||||
postprocessor_kwargs: Additional arguments for the post-processor pipeline.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
"""
|
||||
|
||||
relative_step = RelativeActionsProcessorStep(
|
||||
enabled=config.use_relative_actions,
|
||||
exclude_joints=getattr(config, "relative_exclude_joints", []),
|
||||
|
||||
@@ -28,129 +28,6 @@ DEFAULT_IMAGE_SIZE = 224
|
||||
@PreTrainedConfig.register_subclass("pi05")
|
||||
@dataclass
|
||||
class PI05Config(PreTrainedConfig):
|
||||
"""Configuration class for the PI0.5 flow-matching vision-language-action policy.
|
||||
|
||||
PI0.5 is a PyTorch port of Physical Intelligence's openpi model: a PaliGemma vision-language backbone
|
||||
paired with a smaller Gemma "action expert" that generates action chunks via flow matching. Unlike
|
||||
PI0, it conditions the action expert on the VLM's outputs directly rather than on a separate
|
||||
proprioceptive state projection, and defaults to quantile normalization.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
device (`str | None`, *optional*):
|
||||
Device to run the model on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Auto-detected when
|
||||
`None`.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether the policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Repository ID to push the trained policy to on the Hub.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub repository.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to attach to the policy's Hub repository.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Repo ID on the Hub or local directory to load pretrained weights from. The policy is
|
||||
initialized from scratch when `None`.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (commit hash, branch, or tag) to pin the pretrained model version.
|
||||
paligemma_variant (`str`, *optional*, defaults to `"gemma_2b"`):
|
||||
Which PaliGemma backbone variant to use for the vision-language encoder. Must be
|
||||
`"gemma_2b"` or `"gemma_300m"`.
|
||||
action_expert_variant (`str`, *optional*, defaults to `"gemma_300m"`):
|
||||
Which Gemma variant to use for the action expert network. Must be `"gemma_2b"` or
|
||||
`"gemma_300m"`.
|
||||
dtype (`str`, *optional*, defaults to `"float32"`):
|
||||
Model computation dtype. Must be `"bfloat16"` or `"float32"`.
|
||||
chunk_size (`int`, *optional*, defaults to 50):
|
||||
Number of action steps predicted per model invocation (called "action_horizon" in openpi).
|
||||
n_action_steps (`int`, *optional*, defaults to 50):
|
||||
Number of predicted action steps actually executed in the environment before predicting a new
|
||||
chunk. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the observation state vector is zero-padded to when shorter.
|
||||
max_action_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the action vector is zero-padded to when shorter.
|
||||
num_inference_steps (`int`, *optional*, defaults to 10):
|
||||
Number of flow-matching denoising steps performed at inference time.
|
||||
time_sampling_beta_alpha (`float`, *optional*, defaults to 1.5):
|
||||
Alpha shape parameter of the Beta distribution the flow-matching timestep is sampled from
|
||||
during training.
|
||||
time_sampling_beta_beta (`float`, *optional*, defaults to 1.0):
|
||||
Beta shape parameter of the Beta distribution the flow-matching timestep is sampled from
|
||||
during training.
|
||||
time_sampling_scale (`float`, *optional*, defaults to 0.999):
|
||||
Scale applied to the sampled Beta timestep before `time_sampling_offset` is added.
|
||||
time_sampling_offset (`float`, *optional*, defaults to 0.001):
|
||||
Offset added to the scaled Beta timestep sample.
|
||||
min_period (`float`, *optional*, defaults to 0.004):
|
||||
Minimum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
max_period (`float`, *optional*, defaults to 4.0):
|
||||
Maximum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
use_relative_actions (`bool`, *optional*, defaults to `False`):
|
||||
Whether to convert absolute actions to relative (relative to the current state) before feeding
|
||||
them to the model.
|
||||
relative_exclude_joints (`list[str]`, *optional*):
|
||||
Joint names to keep absolute (excluded from the relative conversion) when
|
||||
`use_relative_actions` is enabled. An empty list means every dimension is made relative.
|
||||
action_feature_names (`list[str] | None`, *optional*):
|
||||
Names of the action dimensions, in order. Populated at runtime from dataset metadata by
|
||||
`make_policy`.
|
||||
rtc_config (`RTCConfig | None`, *optional*):
|
||||
Real-Time Chunking configuration. `None` disables RTC inference.
|
||||
image_resolution (`tuple[int, int]`, *optional*, defaults to `(224, 224)`):
|
||||
Target `(height, width)` images are resized (with padding) to before being fed to the vision
|
||||
encoder.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of empty (zero-padded) camera views to add, for models trained with more cameras than
|
||||
are available at inference/training time.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 200):
|
||||
Maximum token length for the language tokenizer.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Mapping from feature type (`"VISUAL"`, `"STATE"`, `"ACTION"`) to the `NormalizationMode` used
|
||||
for it. Defaults to quantile normalization for state and action, as used by PI0.5.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to enable gradient checkpointing to reduce memory usage during training.
|
||||
compile_model (`bool`, *optional*, defaults to `False`):
|
||||
Whether to compile the model with `torch.compile`.
|
||||
compile_mode (`str`, *optional*, defaults to `"max-autotune"`):
|
||||
The `torch.compile` mode to use when `compile_model` is enabled.
|
||||
freeze_vision_encoder (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the vision encoder's weights during training.
|
||||
train_expert_only (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the entire VLM and train only the action expert and its projections.
|
||||
optimizer_lr (`float`, *optional*, defaults to 2.5e-05):
|
||||
Peak learning rate for the AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`):
|
||||
AdamW `(beta1, beta2)` coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
AdamW epsilon term for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.01):
|
||||
AdamW weight decay coefficient.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0):
|
||||
Maximum gradient norm for clipping.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the learning rate scheduler. Auto-scales down when the total number
|
||||
of training steps is smaller.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Learning rate the scheduler decays to at the end of `scheduler_decay_steps`.
|
||||
"""
|
||||
|
||||
paligemma_variant: str = "gemma_2b"
|
||||
action_expert_variant: str = "gemma_300m"
|
||||
dtype: str = "float32" # Options: "bfloat16", "float32"
|
||||
@@ -227,7 +104,6 @@ class PI05Config(PreTrainedConfig):
|
||||
tokenizer_max_length: int = 200 # see openpi `__post_init__`
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the PaliGemma backbone configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
# Validate configuration
|
||||
@@ -270,7 +146,6 @@ class PI05Config(PreTrainedConfig):
|
||||
self.output_features[ACTION] = action_feature
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -280,7 +155,6 @@ class PI05Config(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -290,15 +164,12 @@ class PI05Config(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -709,17 +709,12 @@ class PI05Pytorch(nn.Module): # see openpi `PI0Pytorch`
|
||||
|
||||
|
||||
class PI05Policy(PreTrainedPolicy):
|
||||
"""PyTorch port of Physical Intelligence's PI0.5 vision-language-action policy, generating action
|
||||
chunks via flow matching.
|
||||
"""
|
||||
"""PI05 Policy for LeRobot."""
|
||||
|
||||
config_class = PI05Config
|
||||
name = "pi05"
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`]. PI0.5 implements Real-Time Chunking
|
||||
inference.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __init__(
|
||||
@@ -727,10 +722,9 @@ class PI05Policy(PreTrainedPolicy):
|
||||
config: PI05Config,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the underlying PI0.5 model from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`PI05Config`): Policy configuration class instance.
|
||||
config: Policy configuration class instance.
|
||||
"""
|
||||
require_package("transformers", extra="pi")
|
||||
super().__init__(config)
|
||||
@@ -765,11 +759,7 @@ class PI05Policy(PreTrainedPolicy):
|
||||
strict: bool = True,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`].
|
||||
|
||||
Additionally remaps checkpoint state-dict keys from the upstream openpi naming convention before
|
||||
loading them, and defaults `strict` to `True` rather than `False`.
|
||||
"""
|
||||
"""Override the from_pretrained method to handle key remapping and display important disclaimer."""
|
||||
print(
|
||||
"The PI05 model is a direct port of the OpenPI implementation. \n"
|
||||
"This implementation follows the original OpenPI structure for compatibility. \n"
|
||||
@@ -934,13 +924,10 @@ class PI05Policy(PreTrainedPolicy):
|
||||
return fixed_state_dict
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.parameters()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the cached action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
"""Reset internal state - called when environment resets."""
|
||||
self._action_queue = deque(maxlen=self.config.n_action_steps)
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
@@ -1029,15 +1016,13 @@ class PI05Policy(PreTrainedPolicy):
|
||||
return images, img_masks
|
||||
|
||||
def prepare_action(self, batch):
|
||||
"""Zero-pad the target action to `config.max_action_dim`."""
|
||||
"""Pad action"""
|
||||
actions = pad_vector(batch[ACTION], self.config.max_action_dim)
|
||||
return actions
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Pops one action off an internal
|
||||
queue, refilling the queue by calling `predict_action_chunk` whenever it is empty.
|
||||
"""
|
||||
"""Select a single action given environment observations."""
|
||||
assert not self._rtc_enabled(), (
|
||||
"RTC is not supported for select_action, use it with predict_action_chunk"
|
||||
)
|
||||
@@ -1054,9 +1039,7 @@ class PI05Policy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs: Unpack[ActionSelectKwargs]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]. Runs the flow-matching
|
||||
sampler (`config.num_inference_steps` denoising steps) to generate the chunk.
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
self.eval()
|
||||
|
||||
# Prepare inputs
|
||||
@@ -1073,19 +1056,13 @@ class PI05Policy(PreTrainedPolicy):
|
||||
return actions
|
||||
|
||||
def forward(self, batch: dict[str, Tensor], reduction: str = "mean") -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`]. Computes the flow-matching loss between
|
||||
the model's predicted and target velocity fields.
|
||||
"""Run the batch through the model and compute the loss for training.
|
||||
|
||||
Args:
|
||||
batch (`dict[str, Tensor]`):
|
||||
A batch of preprocessed, normalized observation/action tensors, as produced by this
|
||||
policy's preprocessor pipeline.
|
||||
reduction (`str`, *optional*, defaults to `"mean"`):
|
||||
How to reduce the per-element loss. `"mean"` returns a scalar mean loss; `"none"` returns
|
||||
per-sample losses of shape `(batch_size,)`, e.g. for RA-BC weighting.
|
||||
|
||||
Returns:
|
||||
`tuple[Tensor, dict]`: The loss and a dict of logging-friendly loss statistics.
|
||||
batch: Training batch containing observations and actions.
|
||||
reduction: How to reduce the loss. Options:
|
||||
- "mean": Return scalar mean loss (default, backward compatible)
|
||||
- "none": Return per-sample losses of shape (batch_size,) for RA-BC weighting
|
||||
"""
|
||||
# Prepare inputs
|
||||
images, img_masks = self._preprocess_images(batch)
|
||||
|
||||
@@ -42,7 +42,9 @@ from .configuration_pi05 import PI05Config
|
||||
@ProcessorStepRegistry.register(name="pi05_prepare_state_tokenizer_processor_step")
|
||||
@dataclass
|
||||
class Pi05PrepareStateTokenizerProcessorStep(ProcessorStep):
|
||||
"""Processor step to prepare the state and tokenize the language input."""
|
||||
"""
|
||||
Processor step to prepare the state and tokenize the language input.
|
||||
"""
|
||||
|
||||
max_state_dim: int = 32
|
||||
task_key: str = "task"
|
||||
@@ -80,7 +82,9 @@ class Pi05PrepareStateTokenizerProcessorStep(ProcessorStep):
|
||||
def transform_features(
|
||||
self, features: dict[PipelineFeatureType, dict[str, PolicyFeature]]
|
||||
) -> dict[PipelineFeatureType, dict[str, PolicyFeature]]:
|
||||
"""This step does not alter the feature definitions."""
|
||||
"""
|
||||
This step does not alter the feature definitions.
|
||||
"""
|
||||
return features
|
||||
|
||||
|
||||
@@ -91,7 +95,8 @@ def make_pi05_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the PI0 policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the PI0 policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -106,12 +111,15 @@ def make_pi05_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`PI05Config`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the PI0 policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
preprocessor_kwargs: Additional arguments for the pre-processor pipeline.
|
||||
postprocessor_kwargs: Additional arguments for the post-processor pipeline.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
"""
|
||||
|
||||
relative_step = RelativeActionsProcessorStep(
|
||||
enabled=config.use_relative_actions,
|
||||
exclude_joints=getattr(config, "relative_exclude_joints", []),
|
||||
|
||||
@@ -28,126 +28,6 @@ DEFAULT_IMAGE_SIZE = 224
|
||||
@PreTrainedConfig.register_subclass("pi0_fast")
|
||||
@dataclass
|
||||
class PI0FastConfig(PreTrainedConfig):
|
||||
"""Configuration class for the PI0-FAST autoregressive vision-language-action policy.
|
||||
|
||||
PI0-FAST is a PyTorch port of Physical Intelligence's openpi FAST model: a PaliGemma vision-language
|
||||
backbone paired with a Gemma action expert that generates actions autoregressively as discrete FAST
|
||||
tokens, rather than via flow matching.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
device (`str | None`, *optional*):
|
||||
Device to run the model on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Auto-detected when
|
||||
`None`.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether the policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Repository ID to push the trained policy to on the Hub.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub repository.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to attach to the policy's Hub repository.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Repo ID on the Hub or local directory to load pretrained weights from. The policy is
|
||||
initialized from scratch when `None`.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (commit hash, branch, or tag) to pin the pretrained model version.
|
||||
paligemma_variant (`str`, *optional*, defaults to `"gemma_2b"`):
|
||||
Which PaliGemma backbone variant to use for the vision-language encoder. Must be
|
||||
`"gemma_2b"` or `"gemma_300m"`.
|
||||
action_expert_variant (`str`, *optional*, defaults to `"gemma_300m"`):
|
||||
Which Gemma variant to use for the action expert network.
|
||||
dtype (`str`, *optional*, defaults to `"float32"`):
|
||||
Model computation dtype. Must be `"bfloat16"` or `"float32"`.
|
||||
chunk_size (`int`, *optional*, defaults to 50):
|
||||
Number of action steps predicted per model invocation (called "action_horizon" in openpi).
|
||||
n_action_steps (`int`, *optional*, defaults to 50):
|
||||
Number of predicted action steps actually executed in the environment before predicting a new
|
||||
chunk. Must not exceed `chunk_size`.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the observation state vector is zero-padded to when shorter.
|
||||
max_action_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the action vector is zero-padded to when shorter.
|
||||
max_action_tokens (`int`, *optional*, defaults to 256):
|
||||
Maximum number of discrete FAST action tokens generated per action chunk.
|
||||
use_relative_actions (`bool`, *optional*, defaults to `False`):
|
||||
Whether to convert absolute actions to relative (relative to the current state) before feeding
|
||||
them to the model.
|
||||
relative_exclude_joints (`list[str]`, *optional*):
|
||||
Joint names to keep absolute (excluded from the relative conversion) when
|
||||
`use_relative_actions` is enabled. An empty list means every dimension is made relative.
|
||||
action_feature_names (`list[str] | None`, *optional*):
|
||||
Names of the action dimensions, in order. Populated at runtime from dataset metadata by
|
||||
`make_policy`.
|
||||
rtc_config (`RTCConfig | None`, *optional*):
|
||||
Real-Time Chunking configuration. `None` disables RTC inference.
|
||||
image_resolution (`tuple[int, int]`, *optional*, defaults to `(224, 224)`):
|
||||
Target `(height, width)` images are resized (with padding) to before being fed to the vision
|
||||
encoder.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of empty (zero-padded) camera views to add, for models trained with more cameras than
|
||||
are available at inference/training time.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 200):
|
||||
Maximum token length for the language tokenizer.
|
||||
text_tokenizer_name (`str`, *optional*, defaults to `"google/paligemma-3b-pt-224"`):
|
||||
Hub identifier of the PaliGemma text tokenizer used for the language prompt.
|
||||
action_tokenizer_name (`str`, *optional*, defaults to `"lerobot/fast-action-tokenizer"`):
|
||||
Hub identifier of the FAST tokenizer used to discretize and decode actions.
|
||||
temperature (`float`, *optional*, defaults to 0.0):
|
||||
Sampling temperature used when autoregressively decoding action tokens. `0.0` means greedy
|
||||
decoding.
|
||||
max_decoding_steps (`int`, *optional*, defaults to 256):
|
||||
Maximum number of autoregressive decoding steps when generating action tokens.
|
||||
fast_skip_tokens (`int`, *optional*, defaults to 128):
|
||||
Number of vocabulary tokens reserved (skipped) between the PaliGemma text vocabulary and the
|
||||
FAST action-token range.
|
||||
validate_action_token_prefix (`bool`, *optional*, defaults to `True`):
|
||||
Whether to assert that decoded action-token sequences start with the expected `"Action: "`
|
||||
prefix.
|
||||
use_kv_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use a key/value cache for faster autoregressive decoding.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Mapping from feature type (`"VISUAL"`, `"STATE"`, `"ACTION"`) to the `NormalizationMode` used
|
||||
for it.
|
||||
gradient_checkpointing (`bool`, *optional*, defaults to `False`):
|
||||
Whether to enable gradient checkpointing to reduce memory usage during training.
|
||||
compile_model (`bool`, *optional*, defaults to `False`):
|
||||
Whether to compile the model with `torch.compile`.
|
||||
compile_mode (`str`, *optional*, defaults to `"max-autotune"`):
|
||||
The `torch.compile` mode to use when `compile_model` is enabled.
|
||||
optimizer_lr (`float`, *optional*, defaults to 2.5e-05):
|
||||
Peak learning rate for the AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`):
|
||||
AdamW `(beta1, beta2)` coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
AdamW epsilon term for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.01):
|
||||
AdamW weight decay coefficient.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0):
|
||||
Maximum gradient norm for clipping.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the learning rate scheduler. Auto-scales down when the total number
|
||||
of training steps is smaller.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Learning rate the scheduler decays to at the end of `scheduler_decay_steps`.
|
||||
"""
|
||||
|
||||
paligemma_variant: str = "gemma_2b"
|
||||
action_expert_variant: str = "gemma_300m"
|
||||
dtype: str = "float32" # Options: "bfloat16", "float32"
|
||||
@@ -220,7 +100,6 @@ class PI0FastConfig(PreTrainedConfig):
|
||||
scheduler_decay_lr: float = 2.5e-6
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the PaliGemma/FAST-tokenizer configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
# Validate configuration
|
||||
@@ -260,7 +139,6 @@ class PI0FastConfig(PreTrainedConfig):
|
||||
self.output_features[ACTION] = action_feature
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -270,7 +148,6 @@ class PI0FastConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -280,15 +157,12 @@ class PI0FastConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -507,7 +507,8 @@ class PI0FastPytorch(nn.Module): # see openpi `PI0Pytorch`
|
||||
max_decoding_steps=None,
|
||||
temperature=0.0,
|
||||
) -> torch.Tensor:
|
||||
"""Inefficient but safe autoregressive decoding for FAST tokens.
|
||||
"""
|
||||
Inefficient but safe autoregressive decoding for FAST tokens.
|
||||
Matches the pattern of _generate_subtask_tokens.
|
||||
TODO: jadechoghari, should we move this logic to PI0FastPolicy class?
|
||||
"""
|
||||
@@ -601,7 +602,8 @@ class PI0FastPytorch(nn.Module): # see openpi `PI0Pytorch`
|
||||
max_decoding_steps=None,
|
||||
temperature=0.0,
|
||||
) -> torch.Tensor:
|
||||
"""Optimized autoregressive decoding for FAST tokens using KV Caching.
|
||||
"""
|
||||
Optimized autoregressive decoding for FAST tokens using KV Caching.
|
||||
|
||||
Greedy decoding stops once every sequence emits the end-of-action marker. The
|
||||
returned tensor keeps its fixed shape, with positions not generated after the
|
||||
@@ -736,9 +738,7 @@ class PI0FastPytorch(nn.Module): # see openpi `PI0Pytorch`
|
||||
|
||||
|
||||
class PI0FastPolicy(PreTrainedPolicy):
|
||||
"""PyTorch port of Physical Intelligence's PI0-FAST vision-language-action policy, generating actions
|
||||
autoregressively as discrete FAST tokens.
|
||||
"""
|
||||
"""PI0Fast Policy for LeRobot."""
|
||||
|
||||
config_class = PI0FastConfig
|
||||
name = "pi0_fast"
|
||||
@@ -748,10 +748,9 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
config: PI0FastConfig,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the underlying PI0-FAST model and its tokenizers from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`PI0FastConfig`): Policy configuration class instance.
|
||||
config: Policy configuration class instance.
|
||||
"""
|
||||
require_package("transformers", extra="pi")
|
||||
require_package("scipy", extra="pi")
|
||||
@@ -807,11 +806,7 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
strict: bool = True,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`].
|
||||
|
||||
Additionally remaps checkpoint state-dict keys from the upstream openpi naming convention before
|
||||
loading them, and defaults `strict` to `True` rather than `False`.
|
||||
"""
|
||||
"""Override the from_pretrained method to handle key remapping and display important disclaimer."""
|
||||
print(
|
||||
"The PI0Fast model is a direct port of the OpenPI implementation. \n"
|
||||
"This implementation follows the original OpenPI structure for compatibility. \n"
|
||||
@@ -917,6 +912,7 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
self, state_dict, model_config
|
||||
): # see openpi `BaseModelConfig, _fix_pytorch_state_dict_keys`
|
||||
"""Fix state dict keys to match current model architecture."""
|
||||
|
||||
fixed_state_dict = {}
|
||||
|
||||
for key, value in state_dict.items():
|
||||
@@ -940,13 +936,10 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
return fixed_state_dict
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.parameters()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the cached action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
"""Reset internal state - called when environment resets."""
|
||||
self._action_queue = deque(maxlen=self.config.n_action_steps)
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
@@ -1035,12 +1028,13 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
return images, img_masks
|
||||
|
||||
def prepare_action(self, batch):
|
||||
"""Zero-pad the target action to `config.max_action_dim`."""
|
||||
"""Pad action"""
|
||||
actions = pad_vector(batch[ACTION], self.config.max_action_dim)
|
||||
return actions
|
||||
|
||||
def _paligemma_tokens_to_act_tokens(self, tokens: torch.Tensor) -> torch.Tensor:
|
||||
"""Converts PaliGemma tokens back to action tokens (inverse of _act_tokens_to_paligemma_tokens).
|
||||
"""
|
||||
Converts PaliGemma tokens back to action tokens (inverse of _act_tokens_to_paligemma_tokens).
|
||||
|
||||
Args:
|
||||
tokens: PaliGemma token IDs
|
||||
@@ -1053,21 +1047,17 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
def decode_actions_with_fast(
|
||||
self, token_ids: list[int], time_horizon: int, action_dim: int, relaxed_decoding: bool = True
|
||||
) -> np.ndarray:
|
||||
"""Decode action token IDs back to continuous action values using the FAST tokenizer.
|
||||
"""
|
||||
Decodes action token IDs back to continuous action values using the FAST tokenizer.
|
||||
|
||||
Args:
|
||||
token_ids (`list[int]`):
|
||||
List of token IDs to decode.
|
||||
time_horizon (`int`):
|
||||
The number of timesteps for actions.
|
||||
action_dim (`int`):
|
||||
The dimensionality of each action.
|
||||
relaxed_decoding (`bool`, *optional*, defaults to `True`):
|
||||
Whether to allow decoded DCT coefficient sequences whose length does not exactly match
|
||||
`time_horizon * action_dim`, truncating or zero-padding them to fit.
|
||||
token_ids: List of token IDs to decode.
|
||||
time_horizon: The number of timesteps for actions.
|
||||
action_dim: The dimensionality of each action.
|
||||
relaxed_decoding: Whether to use relaxed decoding (allows partial sequences).
|
||||
|
||||
Returns:
|
||||
`np.ndarray`: The decoded actions.
|
||||
A numpy array representing the decoded actions.
|
||||
"""
|
||||
decoded_actions = []
|
||||
|
||||
@@ -1111,23 +1101,20 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
return np.stack(decoded_actions)
|
||||
|
||||
def detokenize_actions(self, tokens: torch.Tensor, action_horizon: int, action_dim: int) -> torch.Tensor:
|
||||
"""Detokenize action tokens back to continuous actions.
|
||||
"""
|
||||
Detokenizes action tokens back to continuous actions.
|
||||
|
||||
Converts predicted action tokens from the model back to continuous action values using the FAST
|
||||
tokenizer: converts from PaliGemma token space to action token space, then decodes to continuous
|
||||
values using DCT decoding.
|
||||
This method converts predicted action tokens from the model back to continuous action values
|
||||
using the FAST tokenizer. It handles the conversion from PaliGemma token space to action token
|
||||
space, then decodes the action tokens to continuous values using DCT decoding.
|
||||
|
||||
Args:
|
||||
tokens (`torch.Tensor`):
|
||||
The tokenized model output, of shape `(B, seq_len)` or `(seq_len,)`.
|
||||
action_horizon (`int`):
|
||||
The number of timesteps for actions.
|
||||
action_dim (`int`):
|
||||
The dimensionality of each action.
|
||||
tokens: The input tensor of tokenized outputs. Shape: (B, seq_len) or (seq_len,)
|
||||
action_horizon: The number of timesteps for actions.
|
||||
action_dim: The dimensionality of each action.
|
||||
|
||||
Returns:
|
||||
`torch.Tensor`: The continuous action tensor, of shape `(B, action_horizon, action_dim)` or
|
||||
`(action_horizon, action_dim)`.
|
||||
The continuous action tensor. Shape: (B, action_horizon, action_dim) or (action_horizon, action_dim)
|
||||
"""
|
||||
if self.action_tokenizer is None or self._paligemma_tokenizer is None:
|
||||
raise ValueError(
|
||||
@@ -1203,9 +1190,7 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Pops one action off an internal
|
||||
queue, refilling the queue by calling `predict_action_chunk` whenever it is empty.
|
||||
"""
|
||||
"""Select a single action given environment observations."""
|
||||
assert not self._rtc_enabled(), (
|
||||
"RTC is not supported for select_action, use it with predict_action_chunk"
|
||||
)
|
||||
@@ -1222,10 +1207,7 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], **kwargs: Unpack[ActionSelectKwargs]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]. Autoregressively decodes
|
||||
discrete FAST action tokens (optionally with a key/value cache, see `config.use_kv_cache`) and
|
||||
detokenizes them into a continuous action chunk.
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
self.eval()
|
||||
# Prepare inputs
|
||||
images, img_masks = self._preprocess_images(batch)
|
||||
@@ -1269,17 +1251,8 @@ class PI0FastPolicy(PreTrainedPolicy):
|
||||
return continuous_actions
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`]. Computes the cross-entropy loss between
|
||||
the model's predicted and target discrete FAST action tokens.
|
||||
"""Run the batch through the model and compute the loss for training."""
|
||||
|
||||
Args:
|
||||
batch (`dict[str, Tensor]`):
|
||||
A batch of preprocessed, normalized observation/action tensors, as produced by this
|
||||
policy's preprocessor pipeline. Must contain FAST action tokens and their mask.
|
||||
|
||||
Returns:
|
||||
`tuple[Tensor, dict]`: The loss and a dict of logging-friendly loss statistics.
|
||||
"""
|
||||
# Prepare inputs
|
||||
images, img_masks = self._preprocess_images(batch)
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ from .configuration_pi0_fast import PI0FastConfig
|
||||
@ProcessorStepRegistry.register(name="pi0_fast_prepare_state_tokenizer_processor_step")
|
||||
@dataclass
|
||||
class Pi0FastPrepareStateAndLanguageTokenizerProcessorStep(ProcessorStep):
|
||||
"""Processor step to prepare the state and tokenize the language input."""
|
||||
"""
|
||||
Processor step to prepare the state and tokenize the language input.
|
||||
"""
|
||||
|
||||
max_state_dim: int = 32
|
||||
task_key: str = "task"
|
||||
@@ -81,7 +83,9 @@ class Pi0FastPrepareStateAndLanguageTokenizerProcessorStep(ProcessorStep):
|
||||
def transform_features(
|
||||
self, features: dict[PipelineFeatureType, dict[str, PolicyFeature]]
|
||||
) -> dict[PipelineFeatureType, dict[str, PolicyFeature]]:
|
||||
"""This step does not alter the feature definitions."""
|
||||
"""
|
||||
This step does not alter the feature definitions.
|
||||
"""
|
||||
return features
|
||||
|
||||
|
||||
@@ -92,7 +96,8 @@ def make_pi0_fast_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the PI0Fast policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the PI0Fast policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -107,8 +112,10 @@ def make_pi0_fast_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`PI0FastConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the PI0Fast policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
preprocessor_kwargs: Additional arguments for the pre-processor pipeline.
|
||||
postprocessor_kwargs: Additional arguments for the post-processor pipeline.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
|
||||
@@ -29,6 +29,7 @@ from safetensors.torch import load_model as load_model_as_safetensor
|
||||
from torch import Tensor, nn
|
||||
|
||||
from lerobot.configs import PreTrainedConfig
|
||||
from lerobot.utils.constants import ACTION
|
||||
from lerobot.utils.device_utils import resolve_safetensors_device
|
||||
from lerobot.utils.hub import HubMixin
|
||||
from lerobot.utils.import_utils import _peft_available, require_package
|
||||
@@ -54,27 +55,12 @@ _SINGLE_FILE_SHARD_SIZE = "1TB"
|
||||
|
||||
|
||||
class ActionSelectKwargs(TypedDict, total=False):
|
||||
"""Extra keyword arguments accepted by `select_action`/`predict_action_chunk`.
|
||||
|
||||
**Attributes**:
|
||||
- **noise** (`Tensor | None`) -- Optional pre-sampled noise, for policies whose action generation
|
||||
is stochastic (e.g. diffusion/flow-matching policies), used in place of freshly sampled noise.
|
||||
"""
|
||||
|
||||
noise: Tensor | None
|
||||
|
||||
|
||||
class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
"""Base class for policy models.
|
||||
|
||||
Subclasses must define `config_class` and `name`, and implement `forward`, `predict_action_chunk`,
|
||||
`select_action`, `get_optim_params`, and `reset`. See `docs/source/writing_docstrings.mdx` for the
|
||||
concrete-subclass documentation pattern (config dataclass + this contract's deviations only).
|
||||
|
||||
**Attributes**:
|
||||
- **config_class** (`type[PreTrainedConfig]`) -- The config class this policy expects.
|
||||
- **name** (`str`) -- The registered name of this policy (matches its config's
|
||||
`draccus.ChoiceRegistry` name).
|
||||
"""
|
||||
Base class for policy models.
|
||||
"""
|
||||
|
||||
config_class: None
|
||||
@@ -97,11 +83,6 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
_cp_plan: ClassVar[dict[str, Any] | None] = None
|
||||
|
||||
def __init__(self, config: PreTrainedConfig, *inputs, **kwargs):
|
||||
"""Store `config` on `self.config`. Subclasses build their model in their own `__init__`.
|
||||
|
||||
Raises:
|
||||
ValueError: If `config` is not a `PreTrainedConfig` instance.
|
||||
"""
|
||||
super().__init__()
|
||||
if not isinstance(config, PreTrainedConfig):
|
||||
raise ValueError(
|
||||
@@ -112,11 +93,6 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
self.config = config
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
"""Enforce that every concrete subclass defines `config_class` and `name`.
|
||||
|
||||
Raises:
|
||||
TypeError: If `cls` doesn't define `config_class` or `name`.
|
||||
"""
|
||||
super().__init_subclass__(**kwargs)
|
||||
if not getattr(cls, "config_class", None):
|
||||
raise TypeError(f"Class {cls.__name__} must define 'config_class'")
|
||||
@@ -167,36 +143,9 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
strict: bool = False,
|
||||
**kwargs,
|
||||
) -> T:
|
||||
"""Instantiate the policy from `config` and load its safetensors weights.
|
||||
|
||||
"""
|
||||
The policy is set in evaluation mode by default using `policy.eval()` (dropout modules are
|
||||
deactivated). To train it, you should first set it back in training mode with `policy.train()`.
|
||||
|
||||
Args:
|
||||
pretrained_name_or_path (str | Path): Either the `repo_id` of a model hosted on the Hub, or a
|
||||
path to a directory containing weights saved using `save_pretrained`.
|
||||
config (PreTrainedConfig | None, *optional*): The policy config to use. If `None`, resolved
|
||||
from `pretrained_name_or_path` via `PreTrainedConfig.from_pretrained`.
|
||||
force_download (bool, *optional*, defaults to `False`): Whether to force (re-)downloading the
|
||||
files from the Hub, overriding the existing cache.
|
||||
resume_download (bool | None, *optional*): Deprecated; ignored by the underlying Hub client.
|
||||
proxies (dict | None, *optional*): A dictionary of proxy servers to use by protocol or endpoint.
|
||||
token (str | bool | None, *optional*): The token to use as HTTP bearer authorization for
|
||||
remote files. By default, uses the token cached by `huggingface-cli login`.
|
||||
cache_dir (str | Path | None, *optional*): Path to the folder where cached files are stored.
|
||||
local_files_only (bool, *optional*, defaults to `False`): If `True`, avoid downloading the
|
||||
file and return the path to the local cached file if it exists.
|
||||
revision (str | None, *optional*): Revision on the Hub: a branch name, git tag, or commit id.
|
||||
strict (bool, *optional*, defaults to `False`): Whether to require an exact match between the
|
||||
checkpoint's and the instantiated model's parameter keys.
|
||||
kwargs: Forwarded to `config`'s resolution (when `config` is `None`) and to the policy's
|
||||
constructor.
|
||||
|
||||
Returns:
|
||||
T: The loaded policy, in eval mode, on `config.device`.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the weights file isn't found locally or on the Hub.
|
||||
"""
|
||||
if config is None:
|
||||
config = PreTrainedConfig.from_pretrained(
|
||||
@@ -249,10 +198,8 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_optim_params(self) -> dict:
|
||||
"""Returns the policy-specific parameters dict to be passed on to the optimizer.
|
||||
|
||||
Returns:
|
||||
dict: The policy-specific parameters dict to be passed on to the optimizer.
|
||||
"""
|
||||
Returns the policy-specific parameters dict to be passed on to the optimizer.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -264,6 +211,29 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def drop_queued_actions(self) -> None:
|
||||
"""Discard actions precomputed by earlier ``select_action`` calls.
|
||||
|
||||
Chunking policies answer most control ticks from a queue filled by an
|
||||
earlier forward pass, so a mid-episode change to the conditioning —
|
||||
e.g. a new language instruction — would otherwise only take effect
|
||||
once that queue drains (up to ``chunk_size`` ticks). Dropping the
|
||||
queue forces a fresh forward pass on the next ``select_action``.
|
||||
|
||||
Unlike :meth:`reset` this keeps the rest of the episode state (e.g.
|
||||
observation history), so it does not perturb policies that condition
|
||||
on it. Call it from the thread that calls ``select_action``: it
|
||||
mutates the same queues that thread pops from.
|
||||
|
||||
Policies that keep no action queue inherit a no-op.
|
||||
"""
|
||||
queues = getattr(self, "_queues", None)
|
||||
if isinstance(queues, dict) and ACTION in queues:
|
||||
queues[ACTION].clear()
|
||||
action_queue = getattr(self, "_action_queue", None)
|
||||
if action_queue is not None:
|
||||
action_queue.clear()
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""Whether this policy implements Real-Time Chunking inference semantics."""
|
||||
return False
|
||||
@@ -271,11 +241,10 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
# TODO(aliberts, rcadene): split into 'forward' and 'compute_loss'?
|
||||
@abc.abstractmethod
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict | None]:
|
||||
"""Compute the training loss for a batch of normalized observations and actions.
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation/action tensors,
|
||||
as produced by this policy's preprocessor pipeline.
|
||||
batch (dict[str, Tensor]): _description_
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, dict | None]: The loss and potentially other information. Apart from the loss which
|
||||
@@ -289,13 +258,6 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
|
||||
Child classes using action chunking should use this method within `select_action` to form the action chunk
|
||||
cached for selection.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation tensors.
|
||||
kwargs: See `ActionSelectKwargs`.
|
||||
|
||||
Returns:
|
||||
Tensor: The predicted action chunk.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -305,13 +267,6 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
|
||||
When the model uses a history of observations, or outputs a sequence of actions, this method deals
|
||||
with caching.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation tensors.
|
||||
kwargs: See `ActionSelectKwargs`.
|
||||
|
||||
Returns:
|
||||
Tensor: The single action to execute next.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -360,7 +315,8 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
peft_config=None,
|
||||
peft_cli_overrides: dict | None = None,
|
||||
) -> PreTrainedPolicy:
|
||||
"""Wrap this policy with PEFT adapters for parameter-efficient fine-tuning.
|
||||
"""
|
||||
Wrap this policy with PEFT adapters for parameter-efficient fine-tuning.
|
||||
|
||||
This method is the single entry point for PEFT integration. Subclasses should
|
||||
override `_get_default_peft_targets()` to provide default target modules, and
|
||||
@@ -404,7 +360,8 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
return peft_model
|
||||
|
||||
def _get_default_peft_targets(self) -> dict[str, any] | None:
|
||||
"""Return default PEFT target modules for this policy.
|
||||
"""
|
||||
Return default PEFT target modules for this policy.
|
||||
|
||||
Override this in subclasses to provide policy-specific defaults. These defaults
|
||||
are PEFT-method agnostic - they only specify which modules to target.
|
||||
@@ -413,7 +370,8 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
return None
|
||||
|
||||
def _validate_peft_config(self, peft_config) -> None:
|
||||
"""Validate the PEFT configuration for this policy.
|
||||
"""
|
||||
Validate the PEFT configuration for this policy.
|
||||
|
||||
Override this in subclasses to add policy-specific validation or warnings.
|
||||
The default implementation checks that a pretrained_path exists.
|
||||
@@ -431,7 +389,8 @@ class PreTrainedPolicy(nn.Module, HubMixin, abc.ABC):
|
||||
)
|
||||
|
||||
def _preprocess_peft_cli_overrides(self, cli_overrides: dict, peft_method_type) -> dict:
|
||||
"""Preprocess CLI overrides: rename keys and handle method-specific init_type.
|
||||
"""
|
||||
Preprocess CLI overrides: rename keys and handle method-specific init_type.
|
||||
|
||||
Args:
|
||||
cli_overrides: Dict of CLI options (will be copied, not mutated).
|
||||
|
||||
@@ -46,11 +46,10 @@ class ActionQueue:
|
||||
Args:
|
||||
cfg (RTCConfig): Configuration for Real-Time Chunking behavior.
|
||||
|
||||
**Attributes**:
|
||||
- **queue** (`Tensor | None`) -- Processed actions for robot rollout (time_steps, action_dim).
|
||||
- **original_queue** (`Tensor | None`) -- Original actions for RTC computation (time_steps,
|
||||
action_dim).
|
||||
- **last_index** (`int`) -- Current consumption index in the queue.
|
||||
Attributes:
|
||||
queue (Tensor | None): Processed actions for robot rollout (time_steps, action_dim).
|
||||
original_queue (Tensor | None): Original actions for RTC computation (time_steps, action_dim).
|
||||
last_index (int): Current consumption index in the queue.
|
||||
"""
|
||||
|
||||
def __init__(self, cfg: RTCConfig):
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Real Time Chunking (RTC) and Bidirectional Decoding (BID) configuration classes.
|
||||
"""
|
||||
Real Time Chunking (RTC) and Bidirectional Decoding (BID) configuration classes.
|
||||
|
||||
Based on:
|
||||
- Real Time Chunking: https://www.physicalintelligence.company/research/real_time_chunking
|
||||
|
||||
@@ -27,19 +27,19 @@ from torch import Tensor
|
||||
class DebugStep:
|
||||
"""Container for debug information from a single denoising step.
|
||||
|
||||
**Attributes**:
|
||||
- **step_idx** (`int`) -- Step index/counter.
|
||||
- **x_t** (`Tensor | None`) -- Current latent/state tensor.
|
||||
- **v_t** (`Tensor | None`) -- Velocity from denoiser.
|
||||
- **x1_t** (`Tensor | None`) -- Denoised prediction (x_t - time * v_t).
|
||||
- **correction** (`Tensor | None`) -- Correction gradient tensor.
|
||||
- **err** (`Tensor | None`) -- Weighted error term.
|
||||
- **weights** (`Tensor | None`) -- Prefix attention weights.
|
||||
- **guidance_weight** (`float | Tensor | None`) -- Applied guidance weight.
|
||||
- **time** (`float | Tensor | None`) -- Time parameter.
|
||||
- **inference_delay** (`int | None`) -- Inference delay parameter.
|
||||
- **execution_horizon** (`int | None`) -- Execution horizon parameter.
|
||||
- **metadata** (`dict[str, Any]`) -- Additional metadata.
|
||||
Attributes:
|
||||
step_idx (int): Step index/counter.
|
||||
x_t (Tensor | None): Current latent/state tensor.
|
||||
v_t (Tensor | None): Velocity from denoiser.
|
||||
x1_t (Tensor | None): Denoised prediction (x_t - time * v_t).
|
||||
correction (Tensor | None): Correction gradient tensor.
|
||||
err (Tensor | None): Weighted error term.
|
||||
weights (Tensor | None): Prefix attention weights.
|
||||
guidance_weight (float | Tensor | None): Applied guidance weight.
|
||||
time (float | Tensor | None): Time parameter.
|
||||
inference_delay (int | None): Inference delay parameter.
|
||||
execution_horizon (int | None): Execution horizon parameter.
|
||||
metadata (dict[str, Any]): Additional metadata.
|
||||
"""
|
||||
|
||||
step_idx: int = 0
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Real-Time Chunking (RTC) implementation for LeRobot.
|
||||
"""
|
||||
Real-Time Chunking (RTC) implementation for LeRobot.
|
||||
|
||||
Based on Physical Intelligence's Kinetix implementation:
|
||||
https://github.com/Physical-Intelligence/real-time-chunking-kinetix/blob/main/src/model.py#L214
|
||||
@@ -42,7 +43,6 @@ class RTCProcessor:
|
||||
"""
|
||||
|
||||
def __init__(self, rtc_config: RTCConfig):
|
||||
"""Create the processor; starts a debug `Tracker` when `rtc_config.debug` is set."""
|
||||
self.rtc_config = rtc_config
|
||||
|
||||
self.tracker = None
|
||||
@@ -158,6 +158,7 @@ class RTCProcessor:
|
||||
Reference:
|
||||
https://www.physicalintelligence.company/download/real_time_chunking.pdf
|
||||
"""
|
||||
|
||||
# In the original implementation, the time goes from 0 to 1 and
|
||||
# In our implementation, the time goes from 1 to 0
|
||||
# So we need to invert the time
|
||||
@@ -248,16 +249,6 @@ class RTCProcessor:
|
||||
return result
|
||||
|
||||
def get_prefix_weights(self, start, end, total):
|
||||
"""Build the prefix-attention weight vector, per `rtc_config.prefix_attention_schedule`.
|
||||
|
||||
Args:
|
||||
start: The step at which the weight begins ramping down from 1.0 (`ZEROS`/`LINEAR`/`EXP`).
|
||||
end: The step by which the weight reaches 0.0 (`ONES`/`LINEAR`/`EXP`).
|
||||
total: The length of the returned weight vector.
|
||||
|
||||
Returns:
|
||||
A `(total,)` tensor of weights in `[0, 1]`.
|
||||
"""
|
||||
start = min(start, end)
|
||||
|
||||
if self.rtc_config.prefix_attention_schedule == RTCAttentionSchedule.ZEROS:
|
||||
|
||||
@@ -24,130 +24,6 @@ from ..rtc.configuration_rtc import RTCConfig
|
||||
@PreTrainedConfig.register_subclass("smolvla")
|
||||
@dataclass
|
||||
class SmolVLAConfig(PreTrainedConfig):
|
||||
"""Configuration class for the SmolVLA flow-matching vision-language-action policy.
|
||||
|
||||
SmolVLA pairs a SmolVLM2 vision-language backbone with a smaller flow-matching action expert that
|
||||
cross-attends (or self-attends, depending on `attention_mode`) into the VLM's hidden states to
|
||||
generate action chunks.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Inferred from the
|
||||
dataset when left empty.
|
||||
device (`str | None`, *optional*):
|
||||
Device to run the model on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Auto-detected when
|
||||
`None`.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether the policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Repository ID to push the trained policy to on the Hub.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub repository.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to attach to the policy's Hub repository.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Repo ID on the Hub or local directory to load pretrained weights from. The policy is
|
||||
initialized from scratch when `None`.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (commit hash, branch, or tag) to pin the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 50):
|
||||
Number of action steps predicted per model invocation.
|
||||
n_action_steps (`int`, *optional*, defaults to 50):
|
||||
Number of predicted action steps actually executed in the environment before predicting a new
|
||||
chunk. Must not exceed `chunk_size`.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Mapping from feature type (`"VISUAL"`, `"STATE"`, `"ACTION"`) to the `NormalizationMode` used
|
||||
for it.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the observation state vector is zero-padded to when shorter.
|
||||
max_action_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the action vector is zero-padded to when shorter.
|
||||
resize_imgs_with_padding (`tuple[int, int]`, *optional*, defaults to `(512, 512)`):
|
||||
Target `(width, height)` images are resized (with aspect-ratio-preserving padding) to before
|
||||
being fed to the vision encoder.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of empty (zero-padded) camera views to add, e.g. for the aloha_sim variants that expect
|
||||
extra wrist cameras.
|
||||
adapt_to_pi_aloha (`bool`, *optional*, defaults to `False`):
|
||||
Whether to convert joint and gripper values from the standard Aloha space to the space used by
|
||||
the pi internal runtime the base model was trained with.
|
||||
use_delta_joint_actions_aloha (`bool`, *optional*, defaults to `False`):
|
||||
Whether to convert joint dimensions (gripper excluded) to values relative to the current state
|
||||
before passing them to the model. Not yet ported in LeRobot; raises if enabled.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 48):
|
||||
Maximum token length for the language tokenizer.
|
||||
num_steps (`int`, *optional*, defaults to 10):
|
||||
Number of flow-matching denoising steps performed at inference time.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use a key/value cache in the VLM and action expert for faster inference.
|
||||
freeze_vision_encoder (`bool`, *optional*, defaults to `True`):
|
||||
Whether to freeze the vision encoder's weights during training.
|
||||
train_expert_only (`bool`, *optional*, defaults to `True`):
|
||||
Whether to freeze the VLM and train only the action expert.
|
||||
train_state_proj (`bool`, *optional*, defaults to `True`):
|
||||
Whether to train the state projection layer.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Peak learning rate for the AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`):
|
||||
AdamW `(beta1, beta2)` coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
AdamW epsilon term for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-10):
|
||||
AdamW weight decay coefficient.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 10):
|
||||
Maximum gradient norm for clipping.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the learning rate scheduler.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Learning rate the scheduler decays to at the end of `scheduler_decay_steps`.
|
||||
vlm_model_name (`str`, *optional*, defaults to `"HuggingFaceTB/SmolVLM2-500M-Video-Instruct"`):
|
||||
Hub identifier of the SmolVLM2 backbone to use.
|
||||
load_vlm_weights (`bool`, *optional*, defaults to `False`):
|
||||
Whether to load the VLM's pretrained weights. Set `False` when training the expert from
|
||||
scratch, `True` when initializing from pretrained SmolVLA weights.
|
||||
add_image_special_tokens (`bool`, *optional*, defaults to `False`):
|
||||
Whether to surround image features with special image tokens.
|
||||
attention_mode (`str`, *optional*, defaults to `"cross_attn"`):
|
||||
How the action expert attends into the VLM's hidden states.
|
||||
prefix_length (`int`, *optional*, defaults to -1):
|
||||
Fixed length the VLM prefix (image and language tokens) is padded to. `-1` disables padding.
|
||||
pad_language_to (`str`, *optional*, defaults to `"longest"`):
|
||||
Padding strategy for the language tokenizer, e.g. `"longest"` or `"max_length"`.
|
||||
num_expert_layers (`int`, *optional*, defaults to -1):
|
||||
Number of transformer layers in the action expert. A value `<= 0` uses the same number of
|
||||
layers as the VLM; otherwise the expert has fewer layers.
|
||||
num_vlm_layers (`int`, *optional*, defaults to 16):
|
||||
Number of layers used from the VLM backbone (the first `num_vlm_layers` layers).
|
||||
self_attn_every_n_layers (`int`, *optional*, defaults to 2):
|
||||
Interleave a self-attention layer every `self_attn_every_n_layers` expert layers.
|
||||
expert_width_multiplier (`float`, *optional*, defaults to 0.75):
|
||||
The action expert's hidden size, expressed as a multiplier of the VLM's hidden size.
|
||||
min_period (`float`, *optional*, defaults to 0.004):
|
||||
Minimum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
max_period (`float`, *optional*, defaults to 4.0):
|
||||
Maximum period of the sinusoidal positional encoding used to embed the flow-matching timestep.
|
||||
rtc_config (`RTCConfig | None`, *optional*):
|
||||
Real-Time Chunking configuration. `None` disables RTC inference.
|
||||
compile_model (`bool`, *optional*, defaults to `False`):
|
||||
Whether to compile the model with `torch.compile`.
|
||||
compile_mode (`str`, *optional*, defaults to `"max-autotune"`):
|
||||
The `torch.compile` mode to use when `compile_model` is enabled.
|
||||
"""
|
||||
|
||||
# Input / output structure.
|
||||
n_obs_steps: int = 1
|
||||
chunk_size: int = 50
|
||||
@@ -231,9 +107,9 @@ class SmolVLAConfig(PreTrainedConfig):
|
||||
compile_mode: str = "max-autotune" # Torch compile mode
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the SmolVLM backbone configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
"""Input validation (not exhaustive)."""
|
||||
if self.n_action_steps > self.chunk_size:
|
||||
raise ValueError(
|
||||
f"The chunk size is the upper bound for the number of action steps per model invocation. Got "
|
||||
@@ -245,7 +121,6 @@ class SmolVLAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""Validate and set up input/output features."""
|
||||
for i in range(self.empty_cameras):
|
||||
key = f"{OBS_IMAGES}.empty_camera_{i}"
|
||||
empty_camera = PolicyFeature(
|
||||
@@ -255,7 +130,6 @@ class SmolVLAConfig(PreTrainedConfig):
|
||||
self.input_features[key] = empty_camera
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -265,7 +139,6 @@ class SmolVLAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -275,15 +148,12 @@ class SmolVLAConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return [0]
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""SmolVLA:
|
||||
"""
|
||||
SmolVLA:
|
||||
|
||||
[Paper](https://huggingface.co/papers/2506.01844)
|
||||
|
||||
@@ -139,17 +140,12 @@ def aloha_gripper_from_angular_inv(value):
|
||||
|
||||
|
||||
class SmolVLAPolicy(PreTrainedPolicy):
|
||||
"""SmolVLA vision-language-action policy: a `VLAFlowMatching` model (SmolVLM2 backbone plus a
|
||||
flow-matching action expert) wrapped for training and inference within LeRobot.
|
||||
"""
|
||||
"""Wrapper class around VLAFlowMatching model to train and run inference within LeRobot."""
|
||||
|
||||
config_class = SmolVLAConfig
|
||||
name = "smolvla"
|
||||
|
||||
def supports_rtc(self) -> bool:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.supports_rtc`]. SmolVLA implements Real-Time
|
||||
Chunking inference.
|
||||
"""
|
||||
return True
|
||||
|
||||
def __init__(
|
||||
@@ -157,11 +153,12 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
config: SmolVLAConfig,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the underlying `VLAFlowMatching` model from `config`.
|
||||
|
||||
Args:
|
||||
config (`SmolVLAConfig`): Policy configuration class instance.
|
||||
"""
|
||||
Args:
|
||||
config: Policy configuration class instance or None, in which case the default instantiation of
|
||||
the configuration class is used.
|
||||
"""
|
||||
|
||||
require_package("transformers", extra="smolvla")
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -171,9 +168,7 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the cached action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
"""This should be called whenever the environment is reset."""
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
}
|
||||
@@ -195,7 +190,6 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
model_value.rtc_processor = self.rtc_processor
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.parameters()
|
||||
|
||||
def _get_action_chunk(
|
||||
@@ -238,9 +232,6 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
def predict_action_chunk(
|
||||
self, batch: dict[str, Tensor], noise: Tensor | None = None, **kwargs: Unpack[ActionSelectKwargs]
|
||||
) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]. Runs the flow-matching
|
||||
sampler (`config.num_steps` denoising steps) to generate the chunk.
|
||||
"""
|
||||
self.eval()
|
||||
|
||||
batch = self._prepare_batch(batch)
|
||||
@@ -253,9 +244,13 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
def select_action(
|
||||
self, batch: dict[str, Tensor], noise: Tensor | None = None, **kwargs: Unpack[ActionSelectKwargs]
|
||||
) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`]. Pops one action off an internal
|
||||
queue, refilling the queue by calling `predict_action_chunk` whenever it is empty.
|
||||
"""Select a single action given environment observations.
|
||||
|
||||
This method wraps `select_actions` in order to return one action at a time for execution in the
|
||||
environment. It works by managing the actions in a queue and only calling `select_actions` when the
|
||||
queue is empty.
|
||||
"""
|
||||
|
||||
assert not self._rtc_enabled(), (
|
||||
"RTC is not supported for select_action, use it with predict_action_chunk"
|
||||
)
|
||||
@@ -282,23 +277,15 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
def forward(
|
||||
self, batch: dict[str, Tensor], noise=None, time=None, reduction: str = "mean"
|
||||
) -> dict[str, Tensor]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`]. Computes the flow-matching loss between
|
||||
the model's predicted and target velocity fields.
|
||||
"""Do a full training forward pass to compute the loss.
|
||||
|
||||
Args:
|
||||
batch (`dict[str, Tensor]`):
|
||||
A batch of preprocessed, normalized observation/action tensors, as produced by this
|
||||
policy's preprocessor pipeline.
|
||||
noise (`Tensor | None`, *optional*):
|
||||
Pre-sampled noise for the flow-matching objective. Freshly sampled when `None`.
|
||||
time (`Tensor | None`, *optional*):
|
||||
Pre-sampled flow-matching timestep. Freshly sampled when `None`.
|
||||
reduction (`str`, *optional*, defaults to `"mean"`):
|
||||
How to reduce the per-element loss. `"mean"` returns a scalar mean loss; `"none"` returns
|
||||
per-sample losses of shape `(batch_size,)`, e.g. for RA-BC weighting.
|
||||
|
||||
Returns:
|
||||
`tuple[Tensor, dict]`: The loss and a dict of logging-friendly loss statistics.
|
||||
batch: Training batch containing observations and actions.
|
||||
noise: Optional noise tensor for flow matching.
|
||||
time: Optional time tensor for flow matching.
|
||||
reduction: How to reduce the loss. Options:
|
||||
- "mean": Return scalar mean loss (default, backward compatible)
|
||||
- "none": Return per-sample losses of shape (batch_size,) for RA-BC weighting
|
||||
"""
|
||||
if self.config.adapt_to_pi_aloha:
|
||||
batch[OBS_STATE] = self._pi_aloha_decode_state(batch[OBS_STATE])
|
||||
@@ -420,13 +407,13 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
return actions
|
||||
|
||||
def prepare_state(self, batch):
|
||||
"""Take the latest observation state and zero-pad it to `config.max_state_dim`."""
|
||||
"""Pad state"""
|
||||
state = batch[OBS_STATE][:, -1, :] if batch[OBS_STATE].ndim > 2 else batch[OBS_STATE]
|
||||
state = pad_vector(state, self.config.max_state_dim)
|
||||
return state
|
||||
|
||||
def prepare_action(self, batch):
|
||||
"""Zero-pad the target action to `config.max_action_dim`."""
|
||||
"""Pad action"""
|
||||
actions = pad_vector(batch[ACTION], self.config.max_action_dim)
|
||||
return actions
|
||||
|
||||
@@ -454,12 +441,13 @@ class SmolVLAPolicy(PreTrainedPolicy):
|
||||
|
||||
|
||||
def pad_tensor(tensor, max_len, pad_value=0):
|
||||
"""Efficiently pads a tensor along sequence dimension to match max_len.
|
||||
"""
|
||||
Efficiently pads a tensor along sequence dimension to match max_len.
|
||||
|
||||
Args:
|
||||
tensor (torch.Tensor): Shape (B, L, ...) or (B, L).
|
||||
max_len (int): Fixed sequence length.
|
||||
pad_value (int/float, *optional*, defaults to 0): Value for padding.
|
||||
pad_value (int/float): Value for padding.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: Shape (B, max_len, ...) or (B, max_len).
|
||||
@@ -476,7 +464,8 @@ def pad_tensor(tensor, max_len, pad_value=0):
|
||||
|
||||
|
||||
class VLAFlowMatching(nn.Module):
|
||||
"""SmolVLA
|
||||
"""
|
||||
SmolVLA
|
||||
|
||||
[Paper]()
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ def make_smolvla_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the SmolVLA policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the SmolVLA policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -52,12 +53,13 @@ def make_smolvla_pre_post_processors(
|
||||
2. Unnormalizing the output actions to their original scale.
|
||||
|
||||
Args:
|
||||
config (`SmolVLAConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the SmolVLA policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
"""
|
||||
|
||||
steps = make_default_policy_processor_steps(config, dataset_stats)
|
||||
|
||||
input_steps = [
|
||||
|
||||
@@ -32,125 +32,65 @@ class TDMPCConfig(PreTrainedConfig):
|
||||
Those are: `input_features`, `output_features`, and perhaps `max_random_shift_ratio`.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back). TD-MPC only supports a value of 1; anything else raises in
|
||||
`__post_init__`.
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy
|
||||
is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
n_action_repeats (`int`, *optional*, defaults to 2):
|
||||
The number of times to repeat the action returned by the planning. (hint: Google action
|
||||
repeats in Q-learning or ask your favorite chatbot.)
|
||||
horizon (`int`, *optional*, defaults to 5):
|
||||
Horizon for model predictive control.
|
||||
n_action_steps (`int`, *optional*, defaults to 1):
|
||||
Number of action steps to take from the plan given by model predictive control. This is an
|
||||
alternative to using action repeats. If this is set to more than 1, then we require
|
||||
n_action_repeats: The number of times to repeat the action returned by the planning. (hint: Google
|
||||
action repeats in Q-learning or ask your favorite chatbot)
|
||||
horizon: Horizon for model predictive control.
|
||||
n_action_steps: Number of action steps to take from the plan given by model predictive control. This
|
||||
is an alternative to using action repeats. If this is set to more than 1, then we require
|
||||
`n_action_repeats == 1`, `use_mpc == True` and `n_action_steps <= horizon`. Note that this
|
||||
approach of using multiple steps from the plan is not in the original implementation.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to identity normalization for visual, state, and environment features, and
|
||||
min/max normalization for the action, which TD-MPC assumes lies in `[-1, 1]`.
|
||||
image_encoder_hidden_dim (`int`, *optional*, defaults to 32):
|
||||
Number of channels for the convolutional layers used for image encoding.
|
||||
state_encoder_hidden_dim (`int`, *optional*, defaults to 256):
|
||||
Hidden dimension for MLP used for state vector encoding.
|
||||
latent_dim (`int`, *optional*, defaults to 50):
|
||||
Observation's latent embedding dimension.
|
||||
q_ensemble_size (`int`, *optional*, defaults to 5):
|
||||
Number of Q function estimators to use in an ensemble for uncertainty estimation.
|
||||
mlp_dim (`int`, *optional*, defaults to 512):
|
||||
Hidden dimension of MLPs used for modelling the dynamics encoder, reward function, policy (π),
|
||||
Q ensemble, and V.
|
||||
discount (`float`, *optional*, defaults to 0.9):
|
||||
Discount factor (γ) to use for the reinforcement learning formalism.
|
||||
use_mpc (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use model predictive control. The alternative is to just sample the policy model
|
||||
input_features: A dictionary defining the PolicyFeature of the input data for the policy. The key represents
|
||||
the input data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
output_features: A dictionary defining the PolicyFeature of the output data for the policy. The key represents
|
||||
the output data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
normalization_mapping: A dictionary that maps from a str value of FeatureType (e.g., "STATE", "VISUAL") to
|
||||
a corresponding NormalizationMode (e.g., NormalizationMode.MIN_MAX)
|
||||
image_encoder_hidden_dim: Number of channels for the convolutional layers used for image encoding.
|
||||
state_encoder_hidden_dim: Hidden dimension for MLP used for state vector encoding.
|
||||
latent_dim: Observation's latent embedding dimension.
|
||||
q_ensemble_size: Number of Q function estimators to use in an ensemble for uncertainty estimation.
|
||||
mlp_dim: Hidden dimension of MLPs used for modelling the dynamics encoder, reward function, policy
|
||||
(π), Q ensemble, and V.
|
||||
discount: Discount factor (γ) to use for the reinforcement learning formalism.
|
||||
use_mpc: Whether to use model predictive control. The alternative is to just sample the policy model
|
||||
(π) for each step.
|
||||
cem_iterations (`int`, *optional*, defaults to 6):
|
||||
Number of iterations for the MPPI/CEM loop in MPC.
|
||||
max_std (`float`, *optional*, defaults to 2.0):
|
||||
Maximum standard deviation for actions sampled from the gaussian PDF in CEM.
|
||||
min_std (`float`, *optional*, defaults to 0.05):
|
||||
Minimum standard deviation for noise applied to actions sampled from the policy model (π).
|
||||
cem_iterations: Number of iterations for the MPPI/CEM loop in MPC.
|
||||
max_std: Maximum standard deviation for actions sampled from the gaussian PDF in CEM.
|
||||
min_std: Minimum standard deviation for noise applied to actions sampled from the policy model (π).
|
||||
Doubles up as the minimum standard deviation for actions sampled from the gaussian PDF in CEM.
|
||||
n_gaussian_samples (`int`, *optional*, defaults to 512):
|
||||
Number of samples to draw from the gaussian distribution every CEM iteration. Must be
|
||||
non-zero.
|
||||
n_pi_samples (`int`, *optional*, defaults to 51):
|
||||
Number of samples to draw from the policy / world model rollout every CEM iteration. Can be
|
||||
zero.
|
||||
uncertainty_regularizer_coeff (`float`, *optional*, defaults to 1.0):
|
||||
Coefficient for the uncertainty regularization used when estimating trajectory values (this is
|
||||
the λ coefficient in eqn 4 of FOWM).
|
||||
n_elites (`int`, *optional*, defaults to 50):
|
||||
The number of elite samples to use for updating the gaussian parameters every CEM iteration.
|
||||
elite_weighting_temperature (`float`, *optional*, defaults to 0.5):
|
||||
The temperature to use for softmax weighting (by trajectory value) of the elites, when
|
||||
updating the gaussian parameters for CEM.
|
||||
gaussian_mean_momentum (`float`, *optional*, defaults to 0.1):
|
||||
Momentum (α) used for EMA updates of the mean parameter μ of the gaussian parameters optimized
|
||||
in CEM. Updates are calculated as μ⁻ ← αμ⁻ + (1-α)μ.
|
||||
max_random_shift_ratio (`float`, *optional*, defaults to 0.0476):
|
||||
Maximum random shift (as a proportion of the image size) to apply to the image(s) (in units of
|
||||
pixels) for training-time augmentation. If set to 0, no such augmentation is applied. Note
|
||||
that the input images are assumed to be square for this augmentation.
|
||||
reward_coeff (`float`, *optional*, defaults to 0.5):
|
||||
Loss weighting coefficient for the reward regression loss.
|
||||
expectile_weight (`float`, *optional*, defaults to 0.9):
|
||||
Weighting (τ) used in expectile regression for the state value function (V). `v_pred <
|
||||
v_target` is weighted by τ and `v_pred >= v_target` is weighted by `(1-τ)`. τ is expected to
|
||||
be in `[0, 1]`. Setting τ closer to 1 results in a more "optimistic" V. This is sensible to do
|
||||
because `v_target` is obtained by evaluating the learned state-action value functions (Q) with
|
||||
in-sample actions that may not always be optimal.
|
||||
value_coeff (`float`, *optional*, defaults to 0.1):
|
||||
Loss weighting coefficient for both the state-action value (Q) TD loss, and the state value
|
||||
(V) expectile regression loss.
|
||||
consistency_coeff (`float`, *optional*, defaults to 20.0):
|
||||
Loss weighting coefficient for the consistency loss.
|
||||
advantage_scaling (`float`, *optional*, defaults to 3.0):
|
||||
A factor by which the advantages are scaled prior to exponentiation for advantage weighted
|
||||
regression of the policy (π) estimator parameters. Note that the exponentiated advantages are
|
||||
clamped at 100.0.
|
||||
pi_coeff (`float`, *optional*, defaults to 0.5):
|
||||
Loss weighting coefficient for the action regression loss.
|
||||
temporal_decay_coeff (`float`, *optional*, defaults to 0.5):
|
||||
Exponential decay coefficient for decaying the loss coefficient for future time-steps. Hint:
|
||||
each loss computation involves `horizon` steps worth of actions starting from the current
|
||||
time step.
|
||||
target_model_momentum (`float`, *optional*, defaults to 0.995):
|
||||
Momentum (α) used for EMA updates of the target models. Updates are calculated as ϕ ← αϕ +
|
||||
(1-α)θ where ϕ are the parameters of the target model and θ are the parameters of the model
|
||||
being trained.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0003):
|
||||
Learning rate for the Adam optimizer preset.
|
||||
n_gaussian_samples: Number of samples to draw from the gaussian distribution every CEM iteration. Must
|
||||
be non-zero.
|
||||
n_pi_samples: Number of samples to draw from the policy / world model rollout every CEM iteration. Can
|
||||
be zero.
|
||||
uncertainty_regularizer_coeff: Coefficient for the uncertainty regularization used when estimating
|
||||
trajectory values (this is the λ coefficient in eqn 4 of FOWM).
|
||||
n_elites: The number of elite samples to use for updating the gaussian parameters every CEM iteration.
|
||||
elite_weighting_temperature: The temperature to use for softmax weighting (by trajectory value) of the
|
||||
elites, when updating the gaussian parameters for CEM.
|
||||
gaussian_mean_momentum: Momentum (α) used for EMA updates of the mean parameter μ of the gaussian
|
||||
parameters optimized in CEM. Updates are calculated as μ⁻ ← αμ⁻ + (1-α)μ.
|
||||
max_random_shift_ratio: Maximum random shift (as a proportion of the image size) to apply to the
|
||||
image(s) (in units of pixels) for training-time augmentation. If set to 0, no such augmentation
|
||||
is applied. Note that the input images are assumed to be square for this augmentation.
|
||||
reward_coeff: Loss weighting coefficient for the reward regression loss.
|
||||
expectile_weight: Weighting (τ) used in expectile regression for the state value function (V).
|
||||
v_pred < v_target is weighted by τ and v_pred >= v_target is weighted by (1-τ). τ is expected to
|
||||
be in [0, 1]. Setting τ closer to 1 results in a more "optimistic" V. This is sensible to do
|
||||
because v_target is obtained by evaluating the learned state-action value functions (Q) with
|
||||
in-sample actions that may not be always optimal.
|
||||
value_coeff: Loss weighting coefficient for both the state-action value (Q) TD loss, and the state
|
||||
value (V) expectile regression loss.
|
||||
consistency_coeff: Loss weighting coefficient for the consistency loss.
|
||||
advantage_scaling: A factor by which the advantages are scaled prior to exponentiation for advantage
|
||||
weighted regression of the policy (π) estimator parameters. Note that the exponentiated advantages
|
||||
are clamped at 100.0.
|
||||
pi_coeff: Loss weighting coefficient for the action regression loss.
|
||||
temporal_decay_coeff: Exponential decay coefficient for decaying the loss coefficient for future time-
|
||||
steps. Hint: each loss computation involves `horizon` steps worth of actions starting from the
|
||||
current time step.
|
||||
target_model_momentum: Momentum (α) used for EMA updates of the target models. Updates are calculated
|
||||
as ϕ ← αϕ + (1-α)θ where ϕ are the parameters of the target model and θ are the parameters of the
|
||||
model being trained.
|
||||
"""
|
||||
|
||||
# Input / output structure.
|
||||
@@ -207,9 +147,9 @@ class TDMPCConfig(PreTrainedConfig):
|
||||
optimizer_lr: float = 3e-4
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the planning horizon and network configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
"""Input validation (not exhaustive)."""
|
||||
if self.n_gaussian_samples <= 0:
|
||||
raise ValueError(
|
||||
f"The number of gaussian samples for CEM should be non-zero. Got `{self.n_gaussian_samples=}`"
|
||||
@@ -235,15 +175,12 @@ class TDMPCConfig(PreTrainedConfig):
|
||||
raise ValueError("`n_action_steps` must be less than or equal to `horizon`.")
|
||||
|
||||
def get_optimizer_preset(self) -> AdamConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamConfig(lr=self.optimizer_lr)
|
||||
|
||||
def get_scheduler_preset(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return None
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
# There should only be one image key.
|
||||
if len(self.image_features) > 1:
|
||||
raise ValueError(
|
||||
@@ -259,15 +196,12 @@ class TDMPCConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return list(range(self.horizon + 1))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.horizon))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return list(range(self.horizon))
|
||||
|
||||
@@ -68,11 +68,10 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
config: TDMPCConfig,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the world model (and its EMA target copy) from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`TDMPCConfig`):
|
||||
Policy configuration.
|
||||
config: Policy configuration class instance or None, in which case the default instantiation of
|
||||
the configuration class is used.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -86,14 +85,12 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]."""
|
||||
return self.parameters()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Clears the observation and action queues, and the previous CEM mean used to warm-start MPPI/CEM
|
||||
planning in `plan`.
|
||||
"""
|
||||
Clear observation and action queues. Clear previous means for warm starting of MPPI/CEM. Should be
|
||||
called on `env.reset()`
|
||||
"""
|
||||
self._queues = {
|
||||
OBS_STATE: deque(maxlen=1),
|
||||
@@ -109,11 +106,7 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Encodes the observation and, when `use_mpc` is enabled, plans a trajectory with `plan`; otherwise
|
||||
samples a single action from the policy model (π).
|
||||
"""
|
||||
"""Predict a chunk of actions given environment observations."""
|
||||
batch = {key: torch.stack(list(self._queues[key]), dim=1) for key in batch if key in self._queues}
|
||||
|
||||
# Remove the time dimensions as it is not handled yet.
|
||||
@@ -142,11 +135,7 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`, either repeating a single action
|
||||
`n_action_repeats` times or taking `n_action_steps` from the planned trajectory.
|
||||
"""
|
||||
"""Select a single action given environment observations."""
|
||||
# NOTE: for offline evaluation, we have action in the batch, so we need to pop it out
|
||||
if ACTION in batch:
|
||||
batch.pop(ACTION)
|
||||
@@ -176,14 +165,12 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def plan(self, z: Tensor) -> Tensor:
|
||||
"""Plan a sequence of actions using TD-MPC inference (MPPI/CEM over the learned world model).
|
||||
"""Plan sequence of actions using TD-MPC inference.
|
||||
|
||||
Args:
|
||||
z (`Tensor`):
|
||||
`(batch, latent_dim)` tensor for the initial state.
|
||||
|
||||
z: (batch, latent_dim,) tensor for the initial state.
|
||||
Returns:
|
||||
`Tensor`: `(horizon, batch, action_dim)` tensor for the planned trajectory of actions.
|
||||
(horizon, batch, action_dim,) tensor for the planned trajectory of actions.
|
||||
"""
|
||||
device = get_device_from_parameters(self)
|
||||
|
||||
@@ -272,16 +259,13 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def estimate_value(self, z: Tensor, actions: Tensor):
|
||||
"""Estimate the value of a trajectory as per eqn 4 of the FOWM paper.
|
||||
"""Estimates the value of a trajectory as per eqn 4 of the FOWM paper.
|
||||
|
||||
Args:
|
||||
z (`Tensor`):
|
||||
`(batch, latent_dim)` tensor of initial latent states.
|
||||
actions (`Tensor`):
|
||||
`(horizon, batch, action_dim)` tensor of action trajectories.
|
||||
|
||||
z: (batch, latent_dim) tensor of initial latent states.
|
||||
actions: (horizon, batch, action_dim) tensor of action trajectories.
|
||||
Returns:
|
||||
`Tensor`: `(batch,)` tensor of values.
|
||||
(batch,) tensor of values.
|
||||
"""
|
||||
# Initialize return and running discount factor.
|
||||
G, running_discount = 0, 1
|
||||
@@ -324,11 +308,9 @@ class TDMPCPolicy(PreTrainedPolicy):
|
||||
return G
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
"""Run the batch through the model and compute the loss.
|
||||
|
||||
The loss combines a latent-consistency loss, a reward regression loss, TD and expectile losses for
|
||||
the Q/V value functions, and an advantage-weighted regression loss for the policy (π), each
|
||||
weighted by its corresponding `*_coeff` config field.
|
||||
Returns a dictionary with loss as a tensor, and other information as native floats.
|
||||
"""
|
||||
device = get_device_from_parameters(self)
|
||||
|
||||
@@ -631,7 +613,6 @@ class TDMPCTOLD(nn.Module):
|
||||
Args:
|
||||
z: (*, latent_dim) tensor for the current state's latent representation.
|
||||
a: (*, action_dim) tensor for the action to be applied.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
- (*, latent_dim) tensor for the next state's latent representation.
|
||||
@@ -646,7 +627,6 @@ class TDMPCTOLD(nn.Module):
|
||||
Args:
|
||||
z: (*, latent_dim) tensor for the current state's latent representation.
|
||||
a: (*, action_dim) tensor for the action to be applied.
|
||||
|
||||
Returns:
|
||||
(*, latent_dim) tensor for the next state's latent representation.
|
||||
"""
|
||||
@@ -662,7 +642,6 @@ class TDMPCTOLD(nn.Module):
|
||||
Args:
|
||||
z: (*, latent_dim) tensor for the current state's latent representation.
|
||||
std: The standard deviation of the injected noise.
|
||||
|
||||
Returns:
|
||||
(*, action_dim) tensor for the sampled action.
|
||||
"""
|
||||
@@ -677,7 +656,6 @@ class TDMPCTOLD(nn.Module):
|
||||
|
||||
Args:
|
||||
z: (*, latent_dim) tensor for the current state's latent representation.
|
||||
|
||||
Returns:
|
||||
(*,) tensor of estimated state values.
|
||||
"""
|
||||
@@ -710,7 +688,8 @@ class TDMPCObservationEncoder(nn.Module):
|
||||
"""Encode image and/or state vector observations."""
|
||||
|
||||
def __init__(self, config: TDMPCConfig):
|
||||
"""Creates encoders for pixel and/or state modalities.
|
||||
"""
|
||||
Creates encoders for pixel and/or state modalities.
|
||||
TODO(alexander-soare): The original work allows for multiple images by concatenating them along the
|
||||
channel dimension. Re-implement this capability.
|
||||
"""
|
||||
@@ -837,12 +816,12 @@ def flatten_forward_unflatten(fn: Callable[[Tensor], Tensor], image_tensor: Tens
|
||||
"""Helper to temporarily flatten extra dims at the start of the image tensor.
|
||||
|
||||
Args:
|
||||
fn (`Callable`): A callable expecting a 4D `(B, C, H, W)` image tensor.
|
||||
image_tensor (`Tensor`): An image tensor with any number of leading batch-like dims, e.g.
|
||||
`(*, C, H, W)`.
|
||||
|
||||
fn: Callable that the image tensor will be passed to. It should accept (B, C, H, W) and return
|
||||
(B, *), where * is any number of dimensions.
|
||||
image_tensor: An image tensor of shape (**, C, H, W), where ** is any number of dimensions, generally
|
||||
different from *.
|
||||
Returns:
|
||||
A return value from the callable reshaped to `(*, *)`.
|
||||
A return value from the callable reshaped to (**, *).
|
||||
"""
|
||||
if image_tensor.ndim == 4:
|
||||
return fn(image_tensor)
|
||||
|
||||
@@ -34,7 +34,8 @@ def make_tdmpc_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the TDMPC policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the TDMPC policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations.
|
||||
@@ -47,8 +48,8 @@ def make_tdmpc_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`TDMPCConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the TDMPC policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
|
||||
@@ -30,20 +30,6 @@ from lerobot.utils.feature_utils import build_dataset_frame
|
||||
def populate_queues(
|
||||
queues: dict[str, deque], batch: dict[str, torch.Tensor], exclude_keys: list[str] | None = None
|
||||
):
|
||||
"""Push each `batch` entry onto its matching queue, filling a fresh queue by repeating the first value.
|
||||
|
||||
Keys in `batch` with no matching queue in `queues`, or listed in `exclude_keys`, are skipped.
|
||||
|
||||
Args:
|
||||
queues (`dict`): A mapping from key to a fixed-maxlen `deque`, one per observation/action history
|
||||
to track.
|
||||
batch (`dict`): The latest values to push, keyed the same way as `queues`.
|
||||
exclude_keys (`list[str] | None`, *optional*): Keys to skip even if present in both `batch` and
|
||||
`queues`.
|
||||
|
||||
Returns:
|
||||
`queues`, updated in place (also returned for convenience).
|
||||
"""
|
||||
if exclude_keys is None:
|
||||
exclude_keys = []
|
||||
for key in batch:
|
||||
@@ -78,12 +64,12 @@ def get_dtype_from_parameters(module: nn.Module) -> torch.dtype:
|
||||
|
||||
|
||||
def get_output_shape(module: nn.Module, input_shape: tuple) -> tuple:
|
||||
"""Calculates the output shape of a PyTorch module given an input shape.
|
||||
"""
|
||||
Calculates the output shape of a PyTorch module given an input shape.
|
||||
|
||||
Args:
|
||||
module (nn.Module): a PyTorch module
|
||||
input_shape (tuple): A tuple representing the input shape, e.g., `(batch_size, channels, height,
|
||||
width)`.
|
||||
input_shape (tuple): A tuple representing the input shape, e.g., (batch_size, channels, height, width)
|
||||
|
||||
Returns:
|
||||
tuple: The output shape of the module.
|
||||
@@ -125,10 +111,12 @@ def prepare_observation_for_inference(
|
||||
5. Adding task and robot type information to the dictionary.
|
||||
|
||||
Args:
|
||||
observation (`dict`): A dictionary of NumPy arrays keyed by observation name.
|
||||
device (`device`): The torch device to move the resulting tensors to.
|
||||
task (`str | None`, *optional*): The task string to attach to the observation, if any.
|
||||
robot_type (`str | None`, *optional*): The robot type string to attach to the observation, if any.
|
||||
observation: A dictionary mapping observation names (str) to NumPy
|
||||
array data. For images, the format is expected to be (H, W, C).
|
||||
device: The PyTorch device (e.g., 'cpu' or 'cuda') to which the
|
||||
tensors will be moved.
|
||||
task: An optional string identifier for the current task.
|
||||
robot_type: An optional string identifier for the robot being used.
|
||||
|
||||
Returns:
|
||||
A dictionary where values are PyTorch tensors preprocessed for
|
||||
@@ -164,12 +152,13 @@ def build_inference_frame(
|
||||
tensor-based format suitable for passing to a policy model.
|
||||
|
||||
Args:
|
||||
observation (`dict`): The raw observation dictionary from the robot/environment.
|
||||
device (`device`): The torch device to move the resulting tensors to.
|
||||
ds_features (`dict`): The dataset's feature definitions, used to extract the relevant keys from
|
||||
`observation`.
|
||||
task (`str | None`, *optional*): The task string to attach to the observation, if any.
|
||||
robot_type (`str | None`, *optional*): The robot type string to attach to the observation, if any.
|
||||
observation: The raw observation dictionary, which may contain
|
||||
superfluous keys.
|
||||
device: The target PyTorch device for the final tensors.
|
||||
ds_features: A configuration dictionary that specifies which features
|
||||
to extract from the raw observation.
|
||||
task: An optional string identifier for the current task.
|
||||
robot_type: An optional string identifier for the robot being used.
|
||||
|
||||
Returns:
|
||||
A dictionary of preprocessed tensors ready for model inference.
|
||||
@@ -191,8 +180,10 @@ def make_robot_action(action_tensor: PolicyAction, ds_features: dict[str, dict])
|
||||
action tensor is mapped to a named motor or actuator command.
|
||||
|
||||
Args:
|
||||
action_tensor (`Tensor`): The policy's raw output action tensor.
|
||||
ds_features (`dict`): The dataset's feature definitions, used to name each action dimension.
|
||||
action_tensor: A PyTorch tensor representing the policy's action,
|
||||
typically with a batch dimension (e.g., shape [1, action_dim]).
|
||||
ds_features: A configuration dictionary containing metadata, including
|
||||
the names corresponding to each index of the action tensor.
|
||||
|
||||
Returns:
|
||||
A dictionary mapping action names (e.g., "joint_1_motor") to their
|
||||
@@ -214,7 +205,9 @@ def raise_feature_mismatch_error(
|
||||
provided_features: set[str],
|
||||
expected_features: set[str],
|
||||
) -> None:
|
||||
"""Raises a standardized ValueError for feature mismatches between dataset/environment and policy config."""
|
||||
"""
|
||||
Raises a standardized ValueError for feature mismatches between dataset/environment and policy config.
|
||||
"""
|
||||
missing = expected_features - provided_features
|
||||
extra = provided_features - expected_features
|
||||
# TODO (jadechoghari): provide a dynamic rename map suggestion to the user.
|
||||
@@ -234,7 +227,8 @@ def validate_visual_features_consistency(
|
||||
cfg: PreTrainedConfig,
|
||||
features: dict[str, PolicyFeature],
|
||||
) -> None:
|
||||
"""Validates visual feature consistency between a policy config and provided dataset/environment features.
|
||||
"""
|
||||
Validates visual feature consistency between a policy config and provided dataset/environment features.
|
||||
|
||||
Validation passes if EITHER:
|
||||
- Policy's expected visuals are a subset of dataset (policy uses some cameras, dataset has more)
|
||||
|
||||
@@ -27,184 +27,6 @@ from lerobot.utils.constants import OBS_STATE
|
||||
@PreTrainedConfig.register_subclass("vla_jepa")
|
||||
@dataclass
|
||||
class VLAJEPAConfig(PreTrainedConfig):
|
||||
"""Configuration class for the VLA-JEPA policy.
|
||||
|
||||
VLA-JEPA combines a Qwen3-VL vision-language backbone, a flow-matching (DiT) action head, and an
|
||||
optional V-JEPA2 world model trained to predict future video-frame embeddings from the backbone's
|
||||
action tokens. The world model is an auxiliary training loss only; it is not used at inference.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy. Unused by this policy: the
|
||||
actual observation window is `num_video_frames`, via `observation_delta_indices`.
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the
|
||||
policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 7):
|
||||
The size of the action prediction chunk, in units of environment steps.
|
||||
n_action_steps (`int`, *optional*, defaults to 7):
|
||||
The number of action steps to run in the environment for one invocation of the policy. Must
|
||||
be no greater than `chunk_size`.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to identity normalization for visual features, mean/std for state, and min/max
|
||||
for action features.
|
||||
qwen_model_name (`str`, *optional*, defaults to `"Qwen/Qwen3-VL-2B-Instruct"`):
|
||||
Name or path of the pretrained Qwen3-VL vision-language backbone.
|
||||
jepa_encoder_name (`str`, *optional*, defaults to `"facebook/vjepa2-vitl-fpc64-256"`):
|
||||
Name or path of the pretrained V-JEPA2 encoder used as the world model's (frozen) video
|
||||
target encoder.
|
||||
freeze_qwen (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the Qwen3-VL backbone's parameters during training. Enabling this also
|
||||
disables `enable_world_model`, since no gradient would otherwise flow into it.
|
||||
enable_world_model (`bool`, *optional*, defaults to `True`):
|
||||
Whether to build and train the V-JEPA world-model auxiliary loss and its encoder/predictor
|
||||
modules. Forced to `False` when `freeze_qwen` is `True`.
|
||||
reinit_modules (`list[str] | None`, *optional*):
|
||||
Key prefixes allowed to have shape mismatches when loading pretrained weights, for
|
||||
cross-embodiment transfer to a robot with a different action or state dimensionality (e.g.
|
||||
`["model.action_model.action_encoder", "model.action_model.state_encoder"]`). Mismatched
|
||||
tensors under these prefixes are randomly re-initialized instead of raising; any other
|
||||
mismatch still raises.
|
||||
tokenizer_padding_side (`str`, *optional*, defaults to `"left"`):
|
||||
Padding side used by the Qwen3-VL tokenizer.
|
||||
prompt_template (`str`, *optional*, defaults to `"Your task is {instruction}. Infer the temporal dynamics from frames {actions} and produce the corresponding policy actions {e_actions}."`):
|
||||
Template used to build the language prompt fed to Qwen3-VL, formatted with the task
|
||||
instruction and the action/embodied-action token placeholders.
|
||||
special_action_token (`str`, *optional*, defaults to `"<|action_{}|>"`):
|
||||
Format string for the per-timestep action token(s) added to the tokenizer's vocabulary.
|
||||
embodied_action_token (`str`, *optional*, defaults to `"<|embodied_action|>"`):
|
||||
Special token added to the tokenizer's vocabulary, whose hidden states condition the action
|
||||
head.
|
||||
action_dim (`int`, *optional*, defaults to 7):
|
||||
Dimensionality of the action vector. Overwritten from the dataset's action feature shape in
|
||||
`validate_features`.
|
||||
state_dim (`int`, *optional*, defaults to 8):
|
||||
Dimensionality of the robot state vector. Overwritten from the dataset's state feature shape
|
||||
in `validate_features`, when a state feature is present.
|
||||
num_action_tokens_per_timestep (`int`, *optional*, defaults to 8):
|
||||
Number of action tokens allocated per prompted timestep in the Qwen3-VL prompt.
|
||||
num_embodied_action_tokens_per_instruction (`int`, *optional*, defaults to 32):
|
||||
Number of embodied-action tokens allocated per instruction in the Qwen3-VL prompt; also sets
|
||||
the action head's future-token embedding count.
|
||||
num_inference_timesteps (`int`, *optional*, defaults to 4):
|
||||
Number of integration steps used by the flow-matching action head at inference time.
|
||||
action_hidden_size (`int`, *optional*, defaults to 1024):
|
||||
Hidden dimension of the action head's DiT output projection.
|
||||
action_model_type (`str`, *optional*, defaults to `"DiT-B"`):
|
||||
Named DiT preset (`"DiT-B"`, `"DiT-L"`, or `"DiT-test"`) providing default attention head
|
||||
count and head dimension for the action head, unless overridden by `action_num_heads` and
|
||||
`action_attention_head_dim`.
|
||||
action_num_layers (`int`, *optional*, defaults to 16):
|
||||
Number of transformer blocks in the action head's DiT.
|
||||
action_num_heads (`int | None`, *optional*):
|
||||
Number of attention heads for the action head's DiT. `None` uses the `action_model_type`
|
||||
preset's value.
|
||||
action_attention_head_dim (`int | None`, *optional*):
|
||||
Per-head attention dimension for the action head's DiT. `None` uses the `action_model_type`
|
||||
preset's value.
|
||||
action_dropout (`float`, *optional*, defaults to 0.2):
|
||||
Dropout used in the action head's DiT transformer blocks.
|
||||
action_num_timestep_buckets (`int`, *optional*, defaults to 1000):
|
||||
Number of discrete buckets the continuous flow-matching timestep is quantized into before
|
||||
being embedded.
|
||||
action_noise_beta_alpha (`float`, *optional*, defaults to 1.5):
|
||||
Alpha parameter of the Beta distribution used to sample the flow-matching timestep during
|
||||
training.
|
||||
action_noise_beta_beta (`float`, *optional*, defaults to 1.0):
|
||||
Beta parameter of the Beta distribution used to sample the flow-matching timestep during
|
||||
training.
|
||||
action_noise_s (`float`, *optional*, defaults to 0.999):
|
||||
Scale used to rescale the Beta-sampled value into a flow-matching timestep, as
|
||||
`(action_noise_s - sample) / action_noise_s`.
|
||||
num_target_vision_tokens (`int`, *optional*, defaults to 32):
|
||||
Reserved configuration field; not currently referenced by the model implementation.
|
||||
action_max_seq_len (`int`, *optional*, defaults to 1024):
|
||||
Reserved configuration field; not currently referenced by the model implementation.
|
||||
num_video_frames (`int`, *optional*, defaults to 8):
|
||||
Total number of video frames loaded per sample for the world model.
|
||||
predictor_depth (`int`, *optional*, defaults to 12):
|
||||
Number of transformer blocks in the world model's video predictor.
|
||||
predictor_num_heads (`int`, *optional*, defaults to 8):
|
||||
Number of attention heads in the world model's video predictor.
|
||||
predictor_mlp_ratio (`float`, *optional*, defaults to 4.0):
|
||||
Feed-forward expansion ratio in the world model's video predictor.
|
||||
predictor_dropout (`float`, *optional*, defaults to 0.0):
|
||||
Reserved configuration field; not currently referenced by the model implementation.
|
||||
world_model_loss_weight (`float`, *optional*, defaults to 0.1):
|
||||
Weight applied to the world-model's video-prediction loss before adding it to the action
|
||||
loss.
|
||||
jepa_tubelet_size (`int`, *optional*, defaults to 2):
|
||||
Number of camera views the world model consumes (video tensors are padded or trimmed to this
|
||||
many views), and the fallback tubelet size used to size action-token prompt placeholders
|
||||
when the world model is disabled. Should match the JEPA encoder's actual tubelet size (e.g.
|
||||
2 for `vjepa2-vitl-fpc64-256`).
|
||||
repeated_diffusion_steps (`int`, *optional*, defaults to 8):
|
||||
Number of independent noise draws per batch item used to repeat the flow-matching loss
|
||||
computation (CogACT-style).
|
||||
resize_images_to (`tuple[int, int] | None`, *optional*):
|
||||
Target `(height, width)` to resize input images to before inference. `None` (the default)
|
||||
keeps the original resolution. Not applied during training.
|
||||
binarize_gripper_action (`bool`, *optional*, defaults to `True`):
|
||||
Whether to binarize the gripper action dimension after unnormalization, in the
|
||||
post-processing pipeline built by `make_vla_jepa_pre_post_processors`.
|
||||
pre_snap_gripper_action (`bool`, *optional*, defaults to `True`):
|
||||
Whether to snap the gripper action dimension to `{0, 1}` before unnormalization, in the
|
||||
post-processing pipeline built by `make_vla_jepa_pre_post_processors`.
|
||||
clip_normalized_actions (`bool`, *optional*, defaults to `True`):
|
||||
Whether to clip normalized actions to `[-1, 1]` before unnormalization, in the
|
||||
post-processing pipeline built by `make_vla_jepa_pre_post_processors`.
|
||||
gripper_dim (`int`, *optional*, defaults to 6):
|
||||
Index of the gripper dimension within the action vector, used by the gripper pre/post
|
||||
-processing steps.
|
||||
gripper_threshold (`float`, *optional*, defaults to 0.5):
|
||||
Threshold used by the gripper pre/post-processing steps to binarize the gripper action.
|
||||
torch_dtype (`str`, *optional*, defaults to `"bfloat16"`):
|
||||
Torch dtype (`"float32"`, `"float16"`, or `"bfloat16"`) used to load the Qwen3-VL backbone
|
||||
and (when enabled) the V-JEPA world-model encoder.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Learning rate for the AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`):
|
||||
Adam beta coefficients for the AdamW optimizer preset.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Epsilon for the AdamW optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-10):
|
||||
Weight decay for the AdamW optimizer preset.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 10.0):
|
||||
Gradient-clipping norm for the AdamW optimizer preset.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler preset.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the cosine-decay-with-warmup learning rate scheduler preset.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Final learning rate at the end of decay, for the cosine-decay-with-warmup scheduler preset.
|
||||
"""
|
||||
|
||||
n_obs_steps: int = 1
|
||||
chunk_size: int = 7
|
||||
n_action_steps: int = 7
|
||||
@@ -221,6 +43,11 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
jepa_encoder_name: str = "facebook/vjepa2-vitl-fpc64-256"
|
||||
freeze_qwen: bool = False
|
||||
enable_world_model: bool = True
|
||||
# Enables cross-embodiment transfer: when fine-tuning a pretrained model on a robot with a
|
||||
# different action or state dimensionality, the input/output projection layers must be
|
||||
# re-initialised from scratch while the rest of the network keeps its pretrained weights.
|
||||
# List the key prefixes that are allowed to have shape mismatches; anything else raises an error.
|
||||
# e.g. ["model.action_model.action_encoder", "model.action_model.state_encoder"]
|
||||
reinit_modules: list[str] | None = None
|
||||
|
||||
tokenizer_padding_side: str = "left"
|
||||
@@ -248,14 +75,15 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
num_target_vision_tokens: int = 32
|
||||
action_max_seq_len: int = 1024
|
||||
|
||||
# total video frames loaded per sample
|
||||
num_video_frames: int = 8
|
||||
predictor_depth: int = 12
|
||||
predictor_num_heads: int = 8
|
||||
predictor_mlp_ratio: float = 4.0
|
||||
predictor_dropout: float = 0.0
|
||||
world_model_loss_weight: float = 0.1
|
||||
jepa_tubelet_size: int = 2
|
||||
repeated_diffusion_steps: int = 8
|
||||
jepa_tubelet_size: int = 2 # must match the encoder (e.g. 2 for vjepa2-vitl-fpc64-256)
|
||||
repeated_diffusion_steps: int = 8 # independent noise draws per batch item (CogACT-style)
|
||||
|
||||
resize_images_to: tuple[int, int] | None = None
|
||||
binarize_gripper_action: bool = True
|
||||
@@ -275,7 +103,6 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
scheduler_decay_lr: float = 2.5e-6
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the JEPA world-model and action-head configuration."""
|
||||
super().__post_init__()
|
||||
if self.freeze_qwen and self.enable_world_model:
|
||||
# freezing qwen backbone makes world model training irrelevant since no grad flows
|
||||
@@ -289,7 +116,6 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if not self.image_features:
|
||||
raise ValueError("VLAJEPA requires at least one visual input feature.")
|
||||
if self.action_feature is None:
|
||||
@@ -306,7 +132,6 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
self.input_features[OBS_STATE] = PolicyFeature(type=FeatureType.STATE, shape=shape)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -316,7 +141,6 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> CosineDecayWithWarmupSchedulerConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -326,17 +150,14 @@ class VLAJEPAConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
# load video_horizon frames starting from current timestep: [t, t+1, ..., t+video_horizon-1]
|
||||
# matches original repo's observation_indices=list(range(video_horizon))
|
||||
return list(range(self.num_video_frames))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -62,7 +62,8 @@ def _get_autocast_context(device_type: str, dtype: torch.dtype = torch.bfloat16)
|
||||
|
||||
|
||||
class VLAJEPAModel(nn.Module):
|
||||
"""Native VLA-JEPA model following the original starVLA VLA_JEPA.py.
|
||||
"""
|
||||
Native VLA-JEPA model following the original starVLA VLA_JEPA.py.
|
||||
|
||||
Components:
|
||||
- Qwen3-VL: vision-language backbone for fused embeddings
|
||||
@@ -329,7 +330,8 @@ class VLAJEPAModel(nn.Module):
|
||||
|
||||
|
||||
class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
"""LeRobot adapter for VLA-JEPA.
|
||||
"""
|
||||
LeRobot adapter for VLA-JEPA.
|
||||
|
||||
Converts LeRobot's standard batch format (dict[str, Tensor]) to the batched tensors
|
||||
the native model expects (keeping everything on-device), calls the native model, and
|
||||
@@ -340,15 +342,6 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
name = "vla_jepa"
|
||||
|
||||
def __init__(self, config: VLAJEPAConfig, **kwargs) -> None:
|
||||
"""Build the native VLA-JEPA model: Qwen3-VL backbone, flow-matching action head, and optional
|
||||
V-JEPA world model.
|
||||
|
||||
Args:
|
||||
config (VLAJEPAConfig): The policy configuration.
|
||||
kwargs: Forwarded to the base class. If `dataset_meta` is present, it is used to override
|
||||
`config.state_dim`/`config.action_dim` from the actual dataset's feature shapes, in case
|
||||
`validate_features` read stale dimensions from a pretrained config.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
if dataset_meta := kwargs.get("dataset_meta"):
|
||||
@@ -365,9 +358,6 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Reinitializes the action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
self._queues = {ACTION: deque(maxlen=self.config.n_action_steps)}
|
||||
|
||||
# ---- Format Conversion: LeRobot → Native ----
|
||||
@@ -435,18 +425,7 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
# ---- LeRobot Policy Interface ----
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Converts the batch to the native model's inputs, then runs the flow-matching action loss and,
|
||||
when `enable_world_model` is set, the V-JEPA world-model loss, returning their sum.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation/action tensors.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, dict]: The total loss (`action_loss` plus the weighted `wm_loss`) and a dict of
|
||||
the individual loss terms for logging.
|
||||
"""
|
||||
"""LeRobot train forward: convert → native forward → aggregate losses."""
|
||||
native_output = self.model.forward(**self._prepare_model_inputs(batch, training=True))
|
||||
|
||||
ref = next(iter(native_output.values()))
|
||||
@@ -457,27 +436,11 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
return total_loss, logs
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Returns all of the model's parameters directly (an iterator), rather than a grouped dict.
|
||||
"""
|
||||
return self.model.parameters()
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Converts the batch to the native model's inputs and runs the flow-matching action head's
|
||||
`predict_action` to produce the full predicted chunk.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation tensors.
|
||||
noise (Tensor | None, *optional*): Accepted for interface compatibility with
|
||||
`ActionSelectKwargs`; currently unused.
|
||||
|
||||
Returns:
|
||||
Tensor: The predicted action chunk.
|
||||
"""
|
||||
"""LeRobot inference: convert → native predict → return as Tensor."""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
|
||||
@@ -487,11 +450,7 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`: the queue is refilled with the first
|
||||
`n_action_steps` predicted actions whenever it runs empty.
|
||||
"""
|
||||
"""LeRobot select_action with action queue caching."""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
if len(self._queues[ACTION]) == 0:
|
||||
@@ -505,7 +464,6 @@ class VLAJEPAPolicy(PreTrainedPolicy):
|
||||
pretrained_name_or_path: str | Path,
|
||||
**kwargs,
|
||||
):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`]."""
|
||||
return super().from_pretrained(pretrained_name_or_path, **kwargs)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -107,26 +107,6 @@ def make_vla_jepa_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Construct the pre-processor and post-processor pipelines for the VLA-JEPA policy.
|
||||
|
||||
The pre-processing pipeline renames observation features, adds a batch dimension, moves data to the
|
||||
configured device, and normalizes input/output features.
|
||||
|
||||
The post-processing pipeline unnormalizes both input and output features (unlike the default policy
|
||||
unnormalizer, which only covers output features), and optionally clips normalized actions and
|
||||
snaps/binarizes a gripper action dimension around the unnormalization step, depending on
|
||||
`config.clip_normalized_actions`, `config.pre_snap_gripper_action`, and
|
||||
`config.binarize_gripper_action`.
|
||||
|
||||
Args:
|
||||
config (VLAJEPAConfig): The policy configuration.
|
||||
dataset_stats (dict[str, dict[str, torch.Tensor]] | None, *optional*): Statistics used for
|
||||
normalizing and unnormalizing features. Defaults to `None`.
|
||||
|
||||
Returns:
|
||||
tuple[PolicyProcessorPipeline, PolicyProcessorPipeline]: The configured pre-processor and
|
||||
post-processor pipelines.
|
||||
"""
|
||||
features = {**config.input_features, **config.output_features}
|
||||
steps = make_default_policy_processor_steps(config, dataset_stats)
|
||||
input_steps = [
|
||||
|
||||
@@ -40,108 +40,43 @@ class VQBeTConfig(PreTrainedConfig):
|
||||
- "action" is required as an output key.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 5):
|
||||
Number of environment steps of observation to pass to the policy (the current step and
|
||||
additional steps going back).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy
|
||||
is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
n_action_pred_token (`int`, *optional*, defaults to 3):
|
||||
Total number of current token and future tokens that VQ-BeT predicts.
|
||||
action_chunk_size (`int`, *optional*, defaults to 5):
|
||||
Action chunk size of each action prediction token.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to identity normalization for visual features and min/max normalization for
|
||||
state and action features.
|
||||
vision_backbone (`str`, *optional*, defaults to `"resnet18"`):
|
||||
Name of the torchvision resnet backbone to use for encoding images.
|
||||
crop_shape (`tuple[int, int] | None`, *optional*, defaults to `(84, 84)`):
|
||||
(H, W) shape to crop images to as a preprocessing step for the vision backbone. Must fit
|
||||
within the image size. `None` means no cropping is done.
|
||||
crop_is_random (`bool`, *optional*, defaults to `True`):
|
||||
Whether the crop should be random at training time (it's always a center crop in eval mode).
|
||||
pretrained_backbone_weights (`str | None`, *optional*, defaults to `"ResNet18_Weights.IMAGENET1K_V1"`):
|
||||
Pretrained weights from torchvision to initialize the backbone. `None` means no pretrained
|
||||
weights.
|
||||
use_group_norm (`bool`, *optional*, defaults to `False`):
|
||||
Whether to replace batch normalization with group normalization in the backbone. The group
|
||||
sizes are set to be about 16 (`feature_dim // 16`).
|
||||
spatial_softmax_num_keypoints (`int`, *optional*, defaults to 32):
|
||||
Number of keypoints for SpatialSoftmax.
|
||||
n_vqvae_training_steps (`int`, *optional*, defaults to 20000):
|
||||
Number of optimization steps for training the Residual VQ.
|
||||
vqvae_n_embed (`int`, *optional*, defaults to 16):
|
||||
Number of embedding vectors in the RVQ dictionary (each layer).
|
||||
vqvae_embedding_dim (`int`, *optional*, defaults to 256):
|
||||
Dimension of each embedding vector in the RVQ dictionary.
|
||||
vqvae_enc_hidden_dim (`int`, *optional*, defaults to 128):
|
||||
Size of hidden dimensions of the encoder/decoder part of the Residual VQ-VAE.
|
||||
gpt_block_size (`int`, *optional*, defaults to 500):
|
||||
Max block size of minGPT (should be larger than the number of input tokens).
|
||||
gpt_input_dim (`int`, *optional*, defaults to 512):
|
||||
Size of input of GPT. This is also used as the dimension of observation features.
|
||||
gpt_output_dim (`int`, *optional*, defaults to 512):
|
||||
Size of output dimension of GPT. This is also used as an input dimension of the offset / bin
|
||||
prediction headers.
|
||||
gpt_n_layer (`int`, *optional*, defaults to 8):
|
||||
Number of layers of GPT.
|
||||
gpt_n_head (`int`, *optional*, defaults to 8):
|
||||
Number of heads of GPT.
|
||||
gpt_hidden_dim (`int`, *optional*, defaults to 512):
|
||||
Size of hidden dimensions of GPT.
|
||||
dropout (`float`, *optional*, defaults to 0.1):
|
||||
Dropout rate for GPT.
|
||||
offset_loss_weight (`float`, *optional*, defaults to 10000.0):
|
||||
A constant that is multiplied to the offset loss.
|
||||
primary_code_loss_weight (`float`, *optional*, defaults to 5.0):
|
||||
A constant that is multiplied to the primary code prediction loss.
|
||||
secondary_code_loss_weight (`float`, *optional*, defaults to 0.5):
|
||||
A constant that is multiplied to the secondary code prediction loss.
|
||||
bet_softmax_temperature (`float`, *optional*, defaults to 0.1):
|
||||
Sampling temperature of code for rollout with VQ-BeT.
|
||||
sequentially_select (`bool`, *optional*, defaults to `False`):
|
||||
Whether to select the primary / secondary code sequentially (pick the primary code, then
|
||||
select the secondary code), or at the same time.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Learning rate for the Adam optimizer preset (GPT and other non-VQ-VAE parameters).
|
||||
optimizer_betas (`tuple`, *optional*, defaults to `(0.95, 0.999)`):
|
||||
Adam optimizer's beta coefficients.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Adam optimizer's epsilon for numerical stability.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 1e-06):
|
||||
Weight decay for the Adam optimizer preset.
|
||||
optimizer_vqvae_lr (`float`, *optional*, defaults to 0.001):
|
||||
Learning rate for the VQ-VAE's own Adam optimizer preset.
|
||||
optimizer_vqvae_weight_decay (`float`, *optional*, defaults to 0.0001):
|
||||
Weight decay for the VQ-VAE's own Adam optimizer preset.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 500):
|
||||
Number of warmup steps for the LR scheduler preset.
|
||||
n_obs_steps: Number of environment steps worth of observations to pass to the policy (takes the
|
||||
current step and additional steps going back).
|
||||
n_action_pred_token: Total number of current token and future tokens that VQ-BeT predicts.
|
||||
action_chunk_size: Action chunk size of each action prediction token.
|
||||
input_features: A dictionary defining the PolicyFeature of the input data for the policy. The key represents
|
||||
the input data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
output_features: A dictionary defining the PolicyFeature of the output data for the policy. The key represents
|
||||
the output data name, and the value is PolicyFeature, which consists of FeatureType and shape attributes.
|
||||
normalization_mapping: A dictionary that maps from a str value of FeatureType (e.g., "STATE", "VISUAL") to
|
||||
a corresponding NormalizationMode (e.g., NormalizationMode.MIN_MAX)
|
||||
vision_backbone: Name of the torchvision resnet backbone to use for encoding images.
|
||||
crop_shape: (H, W) shape to crop images to as a preprocessing step for the vision backbone. Must fit
|
||||
within the image size. If None, no cropping is done.
|
||||
crop_is_random: Whether the crop should be random at training time (it's always a center crop in eval
|
||||
mode).
|
||||
pretrained_backbone_weights: Pretrained weights from torchvision to initialize the backbone.
|
||||
`None` means no pretrained weights.
|
||||
use_group_norm: Whether to replace batch normalization with group normalization in the backbone.
|
||||
The group sizes are set to be about 16 (to be precise, feature_dim // 16).
|
||||
spatial_softmax_num_keypoints: Number of keypoints for SpatialSoftmax.
|
||||
n_vqvae_training_steps: Number of optimization steps for training Residual VQ.
|
||||
vqvae_n_embed: Number of embedding vectors in the RVQ dictionary (each layer).
|
||||
vqvae_embedding_dim: Dimension of each embedding vector in the RVQ dictionary.
|
||||
vqvae_enc_hidden_dim: Size of hidden dimensions of Encoder / Decoder part of Residaul VQ-VAE
|
||||
gpt_block_size: Max block size of minGPT (should be larger than the number of input tokens)
|
||||
gpt_input_dim: Size of output input of GPT. This is also used as the dimension of observation features.
|
||||
gpt_output_dim: Size of output dimension of GPT. This is also used as a input dimension of offset / bin prediction headers.
|
||||
gpt_n_layer: Number of layers of GPT
|
||||
gpt_n_head: Number of headers of GPT
|
||||
gpt_hidden_dim: Size of hidden dimensions of GPT
|
||||
dropout: Dropout rate for GPT
|
||||
offset_loss_weight: A constant that is multiplied to the offset loss
|
||||
primary_code_loss_weight: A constant that is multiplied to the primary code prediction loss
|
||||
secondary_code_loss_weight: A constant that is multiplied to the secondary code prediction loss
|
||||
bet_softmax_temperature: Sampling temperature of code for rollout with VQ-BeT
|
||||
sequentially_select: Whether select code of primary / secondary as sequentially (pick primary code,
|
||||
and then select secodnary code), or at the same time.
|
||||
"""
|
||||
|
||||
# Inputs / output structure.
|
||||
@@ -194,16 +129,15 @@ class VQBeTConfig(PreTrainedConfig):
|
||||
scheduler_warmup_steps: int = 500
|
||||
|
||||
def __post_init__(self):
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the VQ-VAE and action-chunking configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
"""Input validation (not exhaustive)."""
|
||||
if not self.vision_backbone.startswith("resnet"):
|
||||
raise ValueError(
|
||||
f"`vision_backbone` must be one of the ResNet variants. Got {self.vision_backbone}."
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_optimizer_preset`]."""
|
||||
return AdamConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -212,14 +146,12 @@ class VQBeTConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> VQBeTSchedulerConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return VQBeTSchedulerConfig(
|
||||
num_warmup_steps=self.scheduler_warmup_steps,
|
||||
num_vqvae_training_steps=self.n_vqvae_training_steps,
|
||||
)
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
# Note: this check was previously performed inside VQBeTRgbEncoder in the form of
|
||||
# assert len(image_keys) == 1
|
||||
if not len(self.image_features) == 1:
|
||||
@@ -244,15 +176,12 @@ class VQBeTConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, 1))
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(1 - self.n_obs_steps, self.n_action_pred_token + self.action_chunk_size - 1))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -38,7 +38,9 @@ from .vqbet_utils import GPT, ResidualVQ
|
||||
|
||||
|
||||
class VQBeTPolicy(PreTrainedPolicy):
|
||||
"""VQ-BeT Policy as per "Behavior Generation with Latent Actions" """
|
||||
"""
|
||||
VQ-BeT Policy as per "Behavior Generation with Latent Actions"
|
||||
"""
|
||||
|
||||
config_class = VQBeTConfig
|
||||
name = "vqbet"
|
||||
@@ -48,11 +50,12 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
config: VQBeTConfig | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Build the VQ-BeT model from `config`.
|
||||
|
||||
"""
|
||||
Args:
|
||||
config (`VQBeTConfig | None`):
|
||||
Policy configuration.
|
||||
config: Policy configuration class instance or None, in which case the default instantiation of
|
||||
the configuration class is used.
|
||||
dataset_stats: Dataset statistics to be used for normalization. If not passed here, it is expected
|
||||
that they will be passed with a call to `load_state_dict` before the policy is used.
|
||||
"""
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -63,12 +66,6 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
|
||||
Splits parameters into three groups: the RVQ-VAE parameters (trained at
|
||||
`optimizer_vqvae_lr`/`optimizer_vqvae_weight_decay`), weight-decayed parameters, and
|
||||
non-weight-decayed parameters.
|
||||
"""
|
||||
vqvae_params = (
|
||||
list(self.vqbet.action_head.vqvae_model.encoder.parameters())
|
||||
+ list(self.vqbet.action_head.vqvae_model.decoder.parameters())
|
||||
@@ -109,9 +106,9 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
]
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`].
|
||||
|
||||
Clears the observation and action queues populated during rollout of the policy.
|
||||
"""
|
||||
Clear observation and action queues. Should be called on `env.reset()`
|
||||
queues are populated during rollout of the policy, they contain the n latest observations and actions
|
||||
"""
|
||||
self._queues = {
|
||||
OBS_IMAGES: deque(maxlen=self.config.n_obs_steps),
|
||||
@@ -121,16 +118,17 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`]."""
|
||||
batch = {k: torch.stack(list(self._queues[k]), dim=1) for k in batch if k in self._queues}
|
||||
actions = self.vqbet(batch, rollout=True)[:, : self.config.action_chunk_size]
|
||||
return actions
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
"""Select a single action given environment observations.
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`, refilling it once it runs dry.
|
||||
This method wraps `select_actions` in order to return one action at a time for execution in the
|
||||
environment. It works by managing the actions in a queue and only calling `select_actions` when the
|
||||
queue is empty.
|
||||
"""
|
||||
# NOTE: for offline evaluation, we have action in the batch, so we need to pop it out
|
||||
if ACTION in batch:
|
||||
@@ -159,11 +157,7 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
return action
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Until the Residual VQ-VAE is discretized, this trains the VQ-VAE (returning its reconstruction
|
||||
loss and codebook usage stats); afterwards it trains VQ-BeT's GPT and prediction heads instead.
|
||||
"""
|
||||
"""Run the batch through the model and compute the loss for training or validation."""
|
||||
batch = dict(batch) # shallow copy so that adding a key doesn't modify the original
|
||||
batch[OBS_IMAGES] = torch.stack([batch[key] for key in self.config.image_features], dim=-4)
|
||||
# VQ-BeT discretizes action using VQ-VAE before training BeT (please refer to section 3.2 in the VQ-BeT paper https://huggingface.co/papers/2403.03181)
|
||||
@@ -187,7 +181,8 @@ class VQBeTPolicy(PreTrainedPolicy):
|
||||
|
||||
|
||||
class SpatialSoftmax(nn.Module):
|
||||
"""Spatial Soft Argmax operation described in "Deep Spatial Autoencoders for Visuomotor Learning" by Finn et al.
|
||||
"""
|
||||
Spatial Soft Argmax operation described in "Deep Spatial Autoencoders for Visuomotor Learning" by Finn et al.
|
||||
(https://huggingface.co/papers/1509.06113). A minimal port of the robomimic implementation.
|
||||
|
||||
At a high level, this takes 2D feature maps (from a convnet/ViT) and returns the "center of mass"
|
||||
@@ -209,9 +204,10 @@ class SpatialSoftmax(nn.Module):
|
||||
"""
|
||||
|
||||
def __init__(self, input_shape, num_kp=None):
|
||||
"""Args:
|
||||
input_shape (list): (C, H, W) input feature map shape.
|
||||
num_kp (int): number of keypoints in output. If None, output will have the same number of channels as input.
|
||||
"""
|
||||
Args:
|
||||
input_shape (list): (C, H, W) input feature map shape.
|
||||
num_kp (int): number of keypoints in output. If None, output will have the same number of channels as input.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
@@ -234,9 +230,9 @@ class SpatialSoftmax(nn.Module):
|
||||
self.register_buffer("pos_grid", torch.cat([pos_x, pos_y], dim=1))
|
||||
|
||||
def forward(self, features: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
features: (B, C, H, W) input feature maps.
|
||||
|
||||
Returns:
|
||||
(B, K, 2) image-space coordinates of keypoints.
|
||||
"""
|
||||
@@ -409,7 +405,8 @@ class VQBeTModel(nn.Module):
|
||||
|
||||
class VQBeTHead(nn.Module):
|
||||
def __init__(self, config: VQBeTConfig):
|
||||
"""VQBeTHead takes output of GPT layers, and pass the feature through bin prediction head (`self.map_to_cbet_preds_bin`), and offset prediction head (`self.map_to_cbet_preds_offset`)
|
||||
"""
|
||||
VQBeTHead takes output of GPT layers, and pass the feature through bin prediction head (`self.map_to_cbet_preds_bin`), and offset prediction head (`self.map_to_cbet_preds_offset`)
|
||||
|
||||
self.map_to_cbet_preds_bin: outputs probability of each code (for each layer).
|
||||
The input dimension of `self.map_to_cbet_preds_bin` is same with the output of GPT,
|
||||
@@ -420,6 +417,7 @@ class VQBeTHead(nn.Module):
|
||||
The input dimension of ` self.map_to_cbet_preds_offset` is same with the output of GPT,
|
||||
and the output dimension of ` self.map_to_cbet_preds_offset` is `self.vqvae_model.vqvae_num_layers (=fixed as 2) * self.config.vqvae_n_embed * config.action_chunk_size * config.action_feature.shape[0]`.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.config = config
|
||||
# init vqvae
|
||||
@@ -575,7 +573,8 @@ class VQBeTHead(nn.Module):
|
||||
}
|
||||
|
||||
def loss_fn(self, pred, target, **kwargs):
|
||||
"""For given ground truth action values (target), and prediction (pred) this function calculates the overall loss.
|
||||
"""
|
||||
for given ground truth action values (target), and prediction (pred) this function calculates the overall loss.
|
||||
|
||||
predicted_action: predicted action chunk (offset + decoded centroids)
|
||||
sampled_centers: sampled centroids (code of RVQ)
|
||||
@@ -704,9 +703,9 @@ class VQBeTRgbEncoder(nn.Module):
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, x: Tensor) -> Tensor:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
x: (B, C, H, W) image tensor with pixel values in [0, 1].
|
||||
|
||||
Returns:
|
||||
(B, D) image feature.
|
||||
"""
|
||||
@@ -727,11 +726,11 @@ class VQBeTRgbEncoder(nn.Module):
|
||||
def _replace_submodules(
|
||||
root_module: nn.Module, predicate: Callable[[nn.Module], bool], func: Callable[[nn.Module], nn.Module]
|
||||
) -> nn.Module:
|
||||
"""Args:
|
||||
"""
|
||||
Args:
|
||||
root_module: The module for which the submodules need to be replaced
|
||||
predicate: Takes a module as an argument and must return True if the that module is to be replaced.
|
||||
func: Takes a module as an argument and returns a new module to replace it with.
|
||||
|
||||
Returns:
|
||||
The root module with its submodules replaced.
|
||||
"""
|
||||
@@ -762,13 +761,15 @@ class VqVae(nn.Module):
|
||||
self,
|
||||
config: VQBeTConfig,
|
||||
):
|
||||
"""VQ-VAE is composed of three parts: encoder, vq_layer, and decoder.
|
||||
"""
|
||||
VQ-VAE is composed of three parts: encoder, vq_layer, and decoder.
|
||||
Encoder and decoder are MLPs consisting of an input, output layer, and hidden layer, respectively.
|
||||
The vq_layer uses residual VQs.
|
||||
|
||||
This class contains functions for training the encoder and decoder along with the residual VQ layer (for training phase 1),
|
||||
as well as functions to help BeT training part in training phase 2.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self.config = config
|
||||
# 'discretized' indicates whether the Residual VQ part is trained or not. (After finishing the training, we set discretized=True)
|
||||
@@ -860,7 +861,9 @@ class VqVae(nn.Module):
|
||||
|
||||
|
||||
class FocalLoss(nn.Module):
|
||||
"""From https://github.com/notmahi/miniBET/blob/main/behavior_transformer/bet.py"""
|
||||
"""
|
||||
From https://github.com/notmahi/miniBET/blob/main/behavior_transformer/bet.py
|
||||
"""
|
||||
|
||||
def __init__(self, gamma: float = 0, size_average: bool = True):
|
||||
super().__init__()
|
||||
|
||||
@@ -35,7 +35,8 @@ def make_vqbet_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the VQ-BeT policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the VQ-BeT policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features, allowing customization to match pretrained configurations.
|
||||
@@ -48,8 +49,8 @@ def make_vqbet_pre_post_processors(
|
||||
2. Unnormalizing the output features to their original scale.
|
||||
|
||||
Args:
|
||||
config (`VQBeTConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the VQ-BeT policy.
|
||||
dataset_stats: A dictionary of statistics for normalization.
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines.
|
||||
|
||||
@@ -22,61 +22,13 @@ from lerobot.utils.constants import ACTION, OBS_STATE
|
||||
@PreTrainedConfig.register_subclass("wall_x")
|
||||
@dataclass
|
||||
class WallXConfig(PreTrainedConfig):
|
||||
"""Configuration class for the Wall-X policy.
|
||||
"""
|
||||
Configuration class for Wall-X policy.
|
||||
|
||||
Wall-X is based on Qwen2.5-VL with action prediction capabilities using flow matching. It supports
|
||||
cross-embodiment robotic control through unified action representations, and multi-modal learning
|
||||
with vision, language, and action data.
|
||||
Wall-X is based on Qwen2.5-VL with action prediction capabilities using flow matching.
|
||||
It supports cross-embodiment robotic control through unified action representations.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1): Number of environment steps of observation to
|
||||
pass to the policy (the current step plus this many additional steps looking back).
|
||||
input_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from input feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, lerobot.configs.types.PolicyFeature] | None`, *optional*): Mapping from output feature name to its `PolicyFeature` (type and shape). Populated automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*): Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. If unset or unavailable, auto-selected on construction.
|
||||
use_amp (`bool`, *optional*, defaults to `False`): Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`): Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`): Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*): Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*): Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*): Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*): License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`pathlib.Path | None`, *optional*): Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*): Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 32): The size of the action prediction chunk
|
||||
(`action_horizon` in Wall-X terminology).
|
||||
n_action_steps (`int`, *optional*, defaults to 32): The number of actions from a predicted
|
||||
chunk that are actually queued for execution. Must not exceed `chunk_size`.
|
||||
max_action_dim (`int`, *optional*, defaults to 20): Maximum action dimension Wall-X supports;
|
||||
shorter actions are zero-padded.
|
||||
max_state_dim (`int`, *optional*, defaults to 20): Maximum proprioceptive-state dimension
|
||||
Wall-X supports; shorter states are zero-padded.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*): Per-feature-type
|
||||
normalization mode; defaults to `IDENTITY` for vision and `MEAN_STD` for state/action.
|
||||
pretrained_name_or_path (`str`, *optional*, defaults to `"x-square-robot/wall-oss-flow"`): Hub id
|
||||
or local path of the pretrained Wall-X model to load.
|
||||
action_tokenizer_path (`str | None`, *optional*, defaults to `"lerobot/fast-action-tokenizer"`): Hub
|
||||
id of the FAST action tokenizer, used only when `prediction_mode="fast"`. Forced to `None` in
|
||||
`__post_init__` when `prediction_mode` is `"diffusion"`.
|
||||
prediction_mode (`str`, *optional*, defaults to `"diffusion"`): Action prediction mode:
|
||||
`"diffusion"` (flow matching) or `"fast"` (discrete FAST tokens).
|
||||
attn_implementation (`str`, *optional*, defaults to `"eager"`): Attention backend for the
|
||||
language/action-token model. Only `"eager"` is currently supported, since Wall-X's
|
||||
bidirectional action-token islands require an explicit attention mask.
|
||||
vision_attn_implementation (`str`, *optional*, defaults to `"auto"`): Attention backend for
|
||||
vision, independent from the text action-token mask: `"auto"` (packed variable-length
|
||||
attention when supported, otherwise per-chunk SDPA), `"sdpa"`, or `"varlen"`.
|
||||
optimizer_lr (`float`, *optional*, defaults to 2e-05): AdamW learning rate.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.95)`): AdamW betas.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08): AdamW epsilon.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.01): AdamW weight decay.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 1.0): Gradient clipping norm.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000): Number of warmup steps for the
|
||||
cosine-decay-with-warmup scheduler.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 100000): Number of decay steps for the
|
||||
scheduler.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 1e-06): Final learning rate at the end of
|
||||
the decay schedule.
|
||||
This config supports multi-modal learning with vision, language, and action data.
|
||||
"""
|
||||
|
||||
# ==================== Input / Output Structure ====================
|
||||
@@ -126,13 +78,6 @@ class WallXConfig(PreTrainedConfig):
|
||||
scheduler_decay_lr: float = 1e-6
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate cross-field constraints and derive `use_fast_tokenizer` from `prediction_mode`.
|
||||
|
||||
Raises:
|
||||
ValueError: If `n_action_steps` exceeds `chunk_size`, if `prediction_mode` is not
|
||||
`"diffusion"` or `"fast"`, if `attn_implementation` is not `"eager"`, or if
|
||||
`vision_attn_implementation` is not one of `"auto"`, `"sdpa"`, or `"varlen"`.
|
||||
"""
|
||||
super().__post_init__()
|
||||
|
||||
# Input validation
|
||||
@@ -206,7 +151,6 @@ class WallXConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_optimizer_preset(self) -> AdamWConfig:
|
||||
"""Return the AdamW optimizer configuration built from the `optimizer_*` fields."""
|
||||
return AdamWConfig(
|
||||
lr=self.optimizer_lr,
|
||||
betas=self.optimizer_betas,
|
||||
@@ -216,7 +160,6 @@ class WallXConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self):
|
||||
"""Return the cosine-decay-with-warmup scheduler configuration built from the `scheduler_*` fields."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -226,15 +169,12 @@ class WallXConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list:
|
||||
"""Return indices for delta observations (None for Wall-X)."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list:
|
||||
"""Return indices for delta actions."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> None:
|
||||
"""Return indices for delta rewards (None for Wall-X)."""
|
||||
return None
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Wall-X: Cross-embodiment robotic control using Qwen2.5-VL with flow matching.
|
||||
"""
|
||||
Wall-X: Cross-embodiment robotic control using Qwen2.5-VL with flow matching.
|
||||
|
||||
[Paper](https://github.com/x2-robot/wall-x)
|
||||
|
||||
@@ -201,7 +202,8 @@ class SinusoidalPosEmb(nn.Module):
|
||||
|
||||
|
||||
class ActionHead(nn.Module):
|
||||
"""Action prediction head with flow matching.
|
||||
"""
|
||||
Action prediction head with flow matching.
|
||||
|
||||
Implements Beta-distributed noise scheduling and temporal embeddings
|
||||
for action sequence prediction.
|
||||
@@ -247,7 +249,8 @@ class ActionHead(nn.Module):
|
||||
return time
|
||||
|
||||
def forward(self, action_chunk, dof_mask=None):
|
||||
"""Process action sequences with noise injection for training.
|
||||
"""
|
||||
Process action sequences with noise injection for training.
|
||||
|
||||
Args:
|
||||
action_chunk: Action sequences [batch, seq_len, action_dim]
|
||||
@@ -349,7 +352,8 @@ _Qwen2_5_VLForAction_Base = Qwen2_5_VLForConditionalGeneration if _wallx_deps_av
|
||||
|
||||
|
||||
class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
"""Qwen2.5 Vision-Language Mixture of Experts model for action processing.
|
||||
"""
|
||||
Qwen2.5 Vision-Language Mixture of Experts model for action processing.
|
||||
|
||||
This model extends the base Qwen2.5 VL model with action token processing capabilities
|
||||
and optional LoRA fine-tuning support.
|
||||
@@ -380,7 +384,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
strict: bool = False,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Load model from pretrained model path.
|
||||
"""
|
||||
Load model from pretrained model path.
|
||||
|
||||
Args:
|
||||
pretrained_model_path (str): Model directory path containing model.safetensors file
|
||||
@@ -483,7 +488,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
flow_loss_weight=1.0,
|
||||
vision_attn_implementation: str = "auto",
|
||||
):
|
||||
"""Initialize the Qwen2.5 VLMoE model for action processing.
|
||||
"""
|
||||
Initialize the Qwen2.5 VLMoE model for action processing.
|
||||
|
||||
Args:
|
||||
config: Model configuration
|
||||
@@ -551,7 +557,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
param.data = param.data.to(torch.float32)
|
||||
|
||||
def define_action_token_id(self):
|
||||
"""Define action token IDs based on tokenizer configuration.
|
||||
"""
|
||||
Define action token IDs based on tokenizer configuration.
|
||||
|
||||
Creates mappings for fast action tokens, proprioception tokens, and general action tokens.
|
||||
"""
|
||||
@@ -574,7 +581,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
}
|
||||
|
||||
def add_lora(self, r=8, lora_alpha=32, target_modules=None, lora_dropout=0.1):
|
||||
"""Add LoRA (Low-Rank Adaptation) adapters to the model.
|
||||
"""
|
||||
Add LoRA (Low-Rank Adaptation) adapters to the model.
|
||||
|
||||
Args:
|
||||
r (int): Rank of adaptation
|
||||
@@ -630,7 +638,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
second_per_grid_ts: torch.Tensor | None = None,
|
||||
attention_mask: torch.Tensor | None = None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Calculate 3D RoPE (Rotary Position Embedding) indices for vision and text tokens.
|
||||
"""
|
||||
Calculate 3D RoPE (Rotary Position Embedding) indices for vision and text tokens.
|
||||
|
||||
This method computes position embeddings that account for the temporal, height, and width
|
||||
dimensions of vision tokens (images/videos) while maintaining standard 1D position embeddings
|
||||
@@ -831,7 +840,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
agent_pos_mask: torch.FloatTensor | None = None,
|
||||
**kwargs,
|
||||
) -> tuple | Qwen2_5_VLACausalLMOutputWithPast:
|
||||
"""Forward pass for training with multi-modal inputs including vision, text, and action data.
|
||||
"""
|
||||
Forward pass for training with multi-modal inputs including vision, text, and action data.
|
||||
|
||||
This method handles the complete forward pass during training, processing various input modalities
|
||||
including images, videos, text, proprioceptive data, and action sequences. It computes losses
|
||||
@@ -1076,7 +1086,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
)
|
||||
|
||||
def predict_action(self, predict_mode: str, **kwargs):
|
||||
"""Predict actions using specified prediction mode.
|
||||
"""
|
||||
Predict actions using specified prediction mode.
|
||||
|
||||
Args:
|
||||
predict_mode (str): Prediction mode, either "fast" or "diffusion"
|
||||
@@ -1123,7 +1134,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
re_generate: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""Multi-modal prediction method supporting text generation, fast action prediction, and diffusion-based action prediction.
|
||||
"""
|
||||
Multi-modal prediction method supporting text generation, fast action prediction, and diffusion-based action prediction.
|
||||
|
||||
This method handles three prediction modes:
|
||||
1. "text": Pure text generation using autoregressive decoding
|
||||
@@ -1411,7 +1423,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
dof_mask = dof_mask.to(inputs_embeds.device).to(torch.float32)
|
||||
|
||||
def step(timestep, noisy_action):
|
||||
"""Single denoising step for diffusion process.
|
||||
"""
|
||||
Single denoising step for diffusion process.
|
||||
|
||||
Args:
|
||||
timestep: Current diffusion timestep
|
||||
@@ -1481,7 +1494,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
return output
|
||||
|
||||
def forward(self, mode: str | None = None, predict_mode: str | None = "text", **kwargs):
|
||||
"""Main forward pass dispatcher for different execution modes.
|
||||
"""
|
||||
Main forward pass dispatcher for different execution modes.
|
||||
|
||||
This method routes execution to appropriate forward functions based on the specified mode:
|
||||
- No mode (None): Training step with gradient disabled
|
||||
@@ -1533,7 +1547,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
agent_pos_mask=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Prepare inputs for autoregressive generation with multi-modal support.
|
||||
"""
|
||||
Prepare inputs for autoregressive generation with multi-modal support.
|
||||
|
||||
This method handles input preparation for generation, including proper slicing of inputs
|
||||
based on cache position, MoE token type management, and multi-modal data handling.
|
||||
@@ -1640,7 +1655,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
self,
|
||||
input_ids: torch.LongTensor | None,
|
||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||
"""Get the number of images and videos for each sample to calculate tensor separation lengths.
|
||||
"""
|
||||
Get the number of images and videos for each sample to calculate tensor separation lengths.
|
||||
|
||||
These parameters are computed directly from input_ids rather than being passed through
|
||||
the processor to avoid unpredictable impacts from interface modifications.
|
||||
@@ -1676,7 +1692,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
input_ids: torch.LongTensor | None = None,
|
||||
**model_kwargs,
|
||||
) -> tuple[torch.LongTensor, dict[str, Any]]:
|
||||
"""Expand inputs for generation with support for multi-modal tensors.
|
||||
"""
|
||||
Expand inputs for generation with support for multi-modal tensors.
|
||||
|
||||
This is an overridden method that supports expanding tensors without a standard batch
|
||||
size dimension, specifically for vision-related tensors:
|
||||
@@ -1793,7 +1810,8 @@ class Qwen2_5_VLMoEForAction(_Qwen2_5_VLForAction_Base): # noqa: N801
|
||||
|
||||
|
||||
class WallXPolicy(PreTrainedPolicy):
|
||||
"""Wall-X policy for cross-embodiment robotic control.
|
||||
"""
|
||||
Wall-X policy for cross-embodiment robotic control.
|
||||
|
||||
Integrates Qwen2.5-VL vision-language model with action prediction
|
||||
using flow matching for continuous action spaces.
|
||||
@@ -1803,13 +1821,6 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
name = "wall_x"
|
||||
|
||||
def __init__(self, config: WallXConfig, **kwargs):
|
||||
"""Load the pretrained Wall-X (Qwen2.5-VL + flow-matching action head) model and reset the action queue.
|
||||
|
||||
Args:
|
||||
config (WallXConfig): Policy configuration; also validated/completed via
|
||||
`config.validate_features()`.
|
||||
kwargs: Unused; accepted for interface compatibility with `PreTrainedPolicy`.
|
||||
"""
|
||||
require_package("transformers", extra="wallx")
|
||||
require_package("peft", extra="wallx")
|
||||
require_package("torchdiffeq", extra="wallx")
|
||||
@@ -1831,13 +1842,13 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Clears the action queue."""
|
||||
"""Reset action queue."""
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
}
|
||||
|
||||
def get_optim_params(self):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`]. Returns all model parameters."""
|
||||
"""Get parameters for optimization."""
|
||||
return self.parameters()
|
||||
|
||||
def preprocess_inputs(
|
||||
@@ -1846,21 +1857,20 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
*,
|
||||
compute_position_ids: bool = False,
|
||||
) -> BatchFeature:
|
||||
"""Convert a batch of LeRobot dataset items to the Wall-X model input format.
|
||||
"""
|
||||
Convert a batch of LeRobot dataset items to Wall-X model input format.
|
||||
|
||||
This processes a batched dictionary where tensors have the batch dimension first.
|
||||
This processes a batched dictionary where tensors have batch dimension first.
|
||||
|
||||
Args:
|
||||
batch (dict[str, Any]): Dictionary with batched tensors, keyed by e.g.
|
||||
`"observation.state"` (shape `(batch_size, state_dim)` or
|
||||
`(batch_size, n_obs_steps, state_dim)`), `"action"` (shape
|
||||
`(batch_size, chunk_size, action_dim)`), `"observation.images.<key>"` (shape
|
||||
`(batch_size, C, H, W)`), and `"task"` (a list of `batch_size` strings).
|
||||
compute_position_ids (bool, *optional*, defaults to `False`): Whether to also compute and
|
||||
include RoPE position ids in the returned model inputs.
|
||||
batch: Dictionary with batched tensors:
|
||||
- "observation.state": (batch_size, state_dim) or (batch_size, n_obs_steps, state_dim)
|
||||
- "action": (batch_size, chunk_size, action_dim)
|
||||
- "observation.images.<key>": (batch_size, C, H, W)
|
||||
- "task": List[str] of length batch_size
|
||||
|
||||
Returns:
|
||||
`BatchFeature`: The batched Wall-X model inputs.
|
||||
BatchFeature containing batched model inputs
|
||||
"""
|
||||
use_fast_tokenizer = self.config.use_fast_tokenizer
|
||||
|
||||
@@ -2034,11 +2044,17 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
return inputs
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
"""
|
||||
Training forward pass using Qwen2_5_VLMoEForAction.
|
||||
|
||||
Runs `preprocess_inputs` on `batch`, then delegates to the underlying
|
||||
`Qwen2_5_VLMoEForAction` model's `forward` in `mode="train"` to compute the flow-matching
|
||||
and/or cross-entropy losses.
|
||||
Args:
|
||||
batch: Dictionary containing preprocessed inputs from preprocess_inputs()
|
||||
Expected keys: input_ids, attention_mask, pixel_values, image_grid_thw,
|
||||
proprioception, agent_pos_mask, action_chunk, dof_mask, moe_token_types,
|
||||
etc.
|
||||
|
||||
Returns:
|
||||
tuple: (loss, loss_dict)
|
||||
"""
|
||||
batch = self.preprocess_inputs(batch, compute_position_ids=True)
|
||||
|
||||
@@ -2066,11 +2082,7 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Delegates to the underlying `Qwen2_5_VLMoEForAction` model in `mode="predict"`, using either
|
||||
flow-matching or FAST decoding depending on `config.prediction_mode`.
|
||||
"""
|
||||
"""Predict action chunk for evaluation."""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
|
||||
@@ -2108,10 +2120,7 @@ class WallXPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor]) -> Tensor:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`.
|
||||
"""
|
||||
"""Select single action for environment execution."""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
|
||||
|
||||
@@ -38,7 +38,8 @@ def make_wall_x_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Constructs pre-processor and post-processor pipelines for the Wall-X policy.
|
||||
"""
|
||||
Constructs pre-processor and post-processor pipelines for the Wall-X policy.
|
||||
|
||||
The pre-processing pipeline prepares input data for the model by:
|
||||
1. Renaming features to match pretrained configurations
|
||||
@@ -51,12 +52,13 @@ def make_wall_x_pre_post_processors(
|
||||
2. Moving data to the CPU
|
||||
|
||||
Args:
|
||||
config (`WallXConfig`): The policy's configuration, providing feature shapes/types and normalization settings.
|
||||
dataset_stats (`dict[str, dict[str, torch.Tensor]] | None`, *optional*): Dataset statistics used to initialize normalization layers.
|
||||
config: The configuration object for the Wall-X policy
|
||||
dataset_stats: A dictionary of statistics for normalization
|
||||
|
||||
Returns:
|
||||
A tuple containing the configured pre-processor and post-processor pipelines
|
||||
"""
|
||||
|
||||
steps = make_default_policy_processor_steps(config, dataset_stats)
|
||||
|
||||
input_steps = [
|
||||
@@ -77,7 +79,8 @@ def make_wall_x_pre_post_processors(
|
||||
|
||||
@ProcessorStepRegistry.register(name="wall_x_task_processor")
|
||||
class WallXTaskProcessor(ComplementaryDataProcessorStep):
|
||||
"""A processor step that ensures the task description is properly formatted for Wall-X.
|
||||
"""
|
||||
A processor step that ensures the task description is properly formatted for Wall-X.
|
||||
|
||||
This step handles task preprocessing similar to Qwen-VL requirements.
|
||||
"""
|
||||
|
||||
@@ -35,11 +35,10 @@ else:
|
||||
|
||||
|
||||
def _translate_vision_config(vision_config: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Translate a vision config from the vendored Florence-2 format to the native format.
|
||||
"""Translate a vision config from the original Microsoft remote-code Florence-2 format
|
||||
(used by existing XVLA checkpoints) to the native ``transformers`` format.
|
||||
|
||||
Translates from the original Microsoft remote-code Florence-2 format (used by existing XVLA
|
||||
checkpoints) to the native ``transformers`` format. Configs already in the native format pass
|
||||
through unchanged.
|
||||
Configs already in the native format pass through unchanged.
|
||||
"""
|
||||
vision = dict(vision_config)
|
||||
model_type = vision.pop("model_type", None)
|
||||
@@ -77,149 +76,19 @@ def _translate_vision_config(vision_config: dict[str, Any]) -> dict[str, Any]:
|
||||
@PreTrainedConfig.register_subclass("xvla")
|
||||
@dataclass
|
||||
class XVLAConfig(PreTrainedConfig):
|
||||
"""Configuration class for the XVLA (Extended Vision-Language-Action) policy.
|
||||
"""
|
||||
Configuration class for the XVLA (Extended Vision-Language-Action) policy so it can
|
||||
plug into the LeRobot training stack.
|
||||
|
||||
Lets the policy plug into the LeRobot training stack. The config mirrors the knobs exposed in the
|
||||
original XVLA repository but also declares the input/output feature contract required by LeRobot.
|
||||
|
||||
Args:
|
||||
n_obs_steps (`int`, *optional*, defaults to 1):
|
||||
Number of environment steps of observation to pass to the policy. Unused by this policy,
|
||||
which always consumes the current-step observation only (`observation_delta_indices` is
|
||||
`None`).
|
||||
input_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from input feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
output_features (`dict[str, PolicyFeature] | None`, *optional*):
|
||||
Mapping from output feature name to its `PolicyFeature` (type and shape). Populated
|
||||
automatically from the dataset when not explicitly provided.
|
||||
device (`str | None`, *optional*):
|
||||
Device the policy runs on, e.g. `"cuda"`, `"cuda:0"`, `"cpu"`, or `"mps"`. Falls back to the
|
||||
best available device if unset or unavailable.
|
||||
use_amp (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use Automatic Mixed Precision for training and evaluation.
|
||||
use_peft (`bool`, *optional*, defaults to `False`):
|
||||
Whether this policy is trained with PEFT (parameter-efficient fine-tuning) adapters.
|
||||
push_to_hub (`bool`, *optional*, defaults to `True`):
|
||||
Whether to push the trained policy to the Hugging Face Hub after training.
|
||||
repo_id (`str | None`, *optional*):
|
||||
Hugging Face Hub repository id to push the policy to, when `push_to_hub` is enabled.
|
||||
private (`bool | None`, *optional*):
|
||||
Whether to create/push the Hub repository as private.
|
||||
tags (`list[str] | None`, *optional*):
|
||||
Tags to attach to the policy's Hub model card.
|
||||
license (`str | None`, *optional*):
|
||||
License identifier to add to the policy's Hub model card.
|
||||
pretrained_path (`Path | None`, *optional*):
|
||||
Path or Hub repo id of pretrained weights to initialize the policy from. If `None`, the
|
||||
policy is initialized from scratch.
|
||||
pretrained_revision (`str | None`, *optional*):
|
||||
Hub revision (branch, tag, or commit hash) pinning the pretrained model version.
|
||||
chunk_size (`int`, *optional*, defaults to 32):
|
||||
The size of the action prediction chunk, in units of environment steps.
|
||||
n_action_steps (`int`, *optional*, defaults to 32):
|
||||
The number of action steps to run in the environment for one invocation of the policy. Must
|
||||
be no greater than `chunk_size`.
|
||||
dtype (`str`, *optional*, defaults to `"float32"`):
|
||||
Torch dtype (`"bfloat16"` or `"float32"`) the model's parameters and inputs are cast to.
|
||||
normalization_mapping (`dict[str, NormalizationMode]`, *optional*):
|
||||
Maps a feature type name (e.g. `"STATE"`, `"VISUAL"`) to the `NormalizationMode` to apply to
|
||||
it. Defaults to identity normalization for all feature types: images are already normalized
|
||||
by the ImageNet processor step, and state/action normalization is handled internally by the
|
||||
action space.
|
||||
florence_config (`dict[str, Any]`, *optional*):
|
||||
Florence-2 vision-language backbone configuration, containing `vision_config` and
|
||||
`text_config`. Accepted in either the native `transformers` format or the original
|
||||
Microsoft remote-code format used by existing XVLA checkpoints; see `get_florence_config`.
|
||||
tokenizer_name (`str`, *optional*, defaults to `"facebook/bart-large"`):
|
||||
Name or path of the tokenizer used to tokenize the language instruction.
|
||||
tokenizer_max_length (`int`, *optional*, defaults to 64):
|
||||
Maximum token length for the tokenized instruction.
|
||||
tokenizer_padding_side (`str`, *optional*, defaults to `"right"`):
|
||||
Padding side used by the tokenizer.
|
||||
pad_language_to (`str`, *optional*, defaults to `"max_length"`):
|
||||
Padding strategy passed to the tokenizer processor step.
|
||||
hidden_size (`int`, *optional*, defaults to 1024):
|
||||
Hidden dimension of the soft-prompted policy transformer head.
|
||||
depth (`int`, *optional*, defaults to 24):
|
||||
Number of transformer layers in the policy transformer head.
|
||||
num_heads (`int`, *optional*, defaults to 16):
|
||||
Number of attention heads in the policy transformer head.
|
||||
mlp_ratio (`float`, *optional*, defaults to 4.0):
|
||||
Feed-forward expansion ratio in the policy transformer head.
|
||||
num_domains (`int`, *optional*, defaults to 30):
|
||||
Number of embodiment domains supported by the domain-conditioned soft prompts.
|
||||
len_soft_prompts (`int`, *optional*, defaults to 32):
|
||||
Number of learned soft-prompt tokens per domain.
|
||||
dim_time (`int`, *optional*, defaults to 32):
|
||||
Embedding dimension for the flow-matching timestep.
|
||||
max_len_seq (`int`, *optional*, defaults to 512):
|
||||
Maximum sequence length supported by the policy transformer head's positional embeddings.
|
||||
use_hetero_proj (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use domain-specific (heterogeneous) input/output projections in the policy
|
||||
transformer head.
|
||||
action_mode (`str`, *optional*, defaults to `"ee6d"`):
|
||||
Name of the action-space representation used by `build_action_space` (e.g. end-effector pose
|
||||
with 6D rotation), which determines the model's action dimensionality and pre/post-processing.
|
||||
num_denoising_steps (`int`, *optional*, defaults to 10):
|
||||
Number of flow-matching integration steps used to generate an action chunk at inference time.
|
||||
use_proprio (`bool`, *optional*, defaults to `True`):
|
||||
Whether to feed a proprioceptive robot state input to the model. Requires a state feature in
|
||||
`input_features` when enabled.
|
||||
max_state_dim (`int`, *optional*, defaults to 32):
|
||||
Dimension the proprioceptive state vector is padded (or truncated) to.
|
||||
max_action_dim (`int`, *optional*, defaults to 20):
|
||||
Maximum action dimension used for padding when `action_mode` is `"auto"`.
|
||||
domain_feature_key (`str | None`, *optional*):
|
||||
Batch key providing a per-sample domain id. Falls back to a `"domain_id"` batch key, then to
|
||||
an all-zeros domain id, when unset or absent from the batch.
|
||||
resize_imgs_with_padding (`tuple[int, int] | None`, *optional*):
|
||||
Target `(height, width)` to resize and pad input images to. `None` keeps the original
|
||||
resolution.
|
||||
num_image_views (`int | None`, *optional*):
|
||||
Total number of camera views the model expects, including padding views. `None` (the
|
||||
default) derives it from the number of image features plus `empty_cameras`; when set
|
||||
explicitly, the larger of the two is used.
|
||||
empty_cameras (`int`, *optional*, defaults to 0):
|
||||
Number of synthetic all-zero camera views added as placeholder input features, e.g. to match
|
||||
a pretrained model's expected view count.
|
||||
freeze_vision_encoder (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the Florence-2 vision encoder's parameters during training.
|
||||
freeze_language_encoder (`bool`, *optional*, defaults to `False`):
|
||||
Whether to freeze the Florence-2 language encoder's parameters during training.
|
||||
train_policy_transformer (`bool`, *optional*, defaults to `True`):
|
||||
Whether the policy transformer head's parameters (other than the soft prompts) are
|
||||
trainable.
|
||||
train_soft_prompts (`bool`, *optional*, defaults to `True`):
|
||||
Whether the domain soft-prompt parameters are trainable.
|
||||
optimizer_lr (`float`, *optional*, defaults to 0.0001):
|
||||
Base learning rate for the XVLA AdamW optimizer preset.
|
||||
optimizer_betas (`tuple[float, float]`, *optional*, defaults to `(0.9, 0.99)`):
|
||||
Adam beta coefficients for the XVLA AdamW optimizer preset.
|
||||
optimizer_eps (`float`, *optional*, defaults to 1e-08):
|
||||
Epsilon for the XVLA AdamW optimizer preset.
|
||||
optimizer_weight_decay (`float`, *optional*, defaults to 0.0):
|
||||
Weight decay for the XVLA AdamW optimizer preset.
|
||||
optimizer_grad_clip_norm (`float`, *optional*, defaults to 10.0):
|
||||
Gradient-clipping norm for the XVLA AdamW optimizer preset.
|
||||
optimizer_soft_prompt_lr_scale (`float`, *optional*, defaults to 1.0):
|
||||
Scale factor applied to `optimizer_lr` for the soft-prompt parameters.
|
||||
optimizer_soft_prompt_warmup_lr_scale (`float | None`, *optional*):
|
||||
Starting scale factor for an optional soft-prompt learning-rate warmup, e.g. `0.01`. `None`
|
||||
disables the warmup.
|
||||
scheduler_warmup_steps (`int`, *optional*, defaults to 1000):
|
||||
Number of warmup steps for the cosine-decay-with-warmup learning rate scheduler preset.
|
||||
scheduler_decay_steps (`int`, *optional*, defaults to 30000):
|
||||
Number of decay steps for the cosine-decay-with-warmup learning rate scheduler preset.
|
||||
scheduler_decay_lr (`float`, *optional*, defaults to 2.5e-06):
|
||||
Final learning rate at the end of decay, for the cosine-decay-with-warmup scheduler preset.
|
||||
The config mirrors the knobs exposed in the original XVLA repository but also
|
||||
declares the input/output feature contract required by LeRobot.
|
||||
"""
|
||||
|
||||
# Input / output structure
|
||||
n_obs_steps: int = 1
|
||||
chunk_size: int = 32
|
||||
n_action_steps: int = 32
|
||||
dtype: str = "float32"
|
||||
dtype: str = "float32" # Options: "bfloat16", "float32"
|
||||
|
||||
normalization_mapping: dict[str, NormalizationMode] = field(
|
||||
default_factory=lambda: {
|
||||
@@ -252,7 +121,7 @@ class XVLAConfig(PreTrainedConfig):
|
||||
num_denoising_steps: int = 10
|
||||
use_proprio: bool = True
|
||||
max_state_dim: int = 32
|
||||
max_action_dim: int = 20
|
||||
max_action_dim: int = 20 # Maximum action dimension for padding (used by "auto" action mode)
|
||||
domain_feature_key: str | None = None
|
||||
|
||||
# Vision preprocessing
|
||||
@@ -260,12 +129,12 @@ class XVLAConfig(PreTrainedConfig):
|
||||
num_image_views: int | None = None
|
||||
empty_cameras: int = 0
|
||||
|
||||
# Freezing options for VLM components.
|
||||
# By default, VLM encoders are frozen and only policy transformer + soft prompts train.
|
||||
freeze_vision_encoder: bool = False
|
||||
freeze_language_encoder: bool = False
|
||||
train_policy_transformer: bool = True
|
||||
train_soft_prompts: bool = True
|
||||
# Freezing options for VLM components
|
||||
# By default, VLM encoders are frozen and only policy transformer + soft prompts train
|
||||
freeze_vision_encoder: bool = False # Freeze VLM vision encoder weights
|
||||
freeze_language_encoder: bool = False # Freeze VLM language encoder weights
|
||||
train_policy_transformer: bool = True # Allow policy transformer to train
|
||||
train_soft_prompts: bool = True # Allow soft prompts to train
|
||||
|
||||
# Training presets
|
||||
optimizer_lr: float = 1e-4
|
||||
@@ -273,15 +142,15 @@ class XVLAConfig(PreTrainedConfig):
|
||||
optimizer_eps: float = 1e-8
|
||||
optimizer_weight_decay: float = 0.0
|
||||
optimizer_grad_clip_norm: float = 10.0
|
||||
optimizer_soft_prompt_lr_scale: float = 1.0
|
||||
optimizer_soft_prompt_warmup_lr_scale: float | None = None
|
||||
# Soft-prompt LR settings (for optional warm-up)
|
||||
optimizer_soft_prompt_lr_scale: float = 1.0 # Scale factor for soft-prompt LR
|
||||
optimizer_soft_prompt_warmup_lr_scale: float | None = None # Start scale for warmup (e.g., 0.01)
|
||||
|
||||
scheduler_warmup_steps: int = 1_000
|
||||
scheduler_decay_steps: int = 30_000
|
||||
scheduler_decay_lr: float = 2.5e-6
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Resolve `device` (see [`~configs.PreTrainedConfig.__post_init__`]), then validate this config. Validates the vendored Florence-2 backbone configuration."""
|
||||
super().__post_init__()
|
||||
|
||||
if self.chunk_size <= 0:
|
||||
@@ -297,7 +166,8 @@ class XVLAConfig(PreTrainedConfig):
|
||||
self._florence_config_obj: Florence2Config | None = None
|
||||
|
||||
def get_florence_config(self) -> Florence2Config:
|
||||
"""Build (and cache) the native ``transformers`` Florence-2 config that backs the VLM.
|
||||
"""
|
||||
Build (and cache) the native ``transformers`` Florence-2 config that backs the VLM.
|
||||
|
||||
``florence_config`` may be given either in the native ``transformers`` format or in the
|
||||
original Microsoft remote-code format stored by existing XVLA checkpoints (e.g. with
|
||||
@@ -335,7 +205,6 @@ class XVLAConfig(PreTrainedConfig):
|
||||
return self._florence_config_obj
|
||||
|
||||
def validate_features(self) -> None:
|
||||
"""See [`~configs.PreTrainedConfig.validate_features`]."""
|
||||
if not self.image_features:
|
||||
raise ValueError("XVLA requires at least one visual feature in the inputs.")
|
||||
if self.use_proprio and self.robot_state_feature is None:
|
||||
@@ -376,7 +245,6 @@ class XVLAConfig(PreTrainedConfig):
|
||||
)
|
||||
|
||||
def get_scheduler_preset(self) -> CosineDecayWithWarmupSchedulerConfig:
|
||||
"""See [`~configs.PreTrainedConfig.get_scheduler_preset`]."""
|
||||
return CosineDecayWithWarmupSchedulerConfig(
|
||||
peak_lr=self.optimizer_lr,
|
||||
decay_lr=self.scheduler_decay_lr,
|
||||
@@ -386,15 +254,12 @@ class XVLAConfig(PreTrainedConfig):
|
||||
|
||||
@property
|
||||
def observation_delta_indices(self) -> list[int] | None:
|
||||
"""See [`~configs.PreTrainedConfig.observation_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def action_delta_indices(self) -> list[int]:
|
||||
"""See [`~configs.PreTrainedConfig.action_delta_indices`]."""
|
||||
return list(range(self.chunk_size))
|
||||
|
||||
@property
|
||||
def reward_delta_indices(self) -> list[int] | None:
|
||||
"""See [`~configs.PreTrainedConfig.reward_delta_indices`]."""
|
||||
return None
|
||||
|
||||
@@ -49,7 +49,9 @@ else:
|
||||
|
||||
|
||||
class XVLAModel(nn.Module):
|
||||
"""XVLA backbone that stitches Florence-2 embeddings with the temporal/action transformer head."""
|
||||
"""
|
||||
XVLA backbone that stitches Florence-2 embeddings with the temporal/action transformer head.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -117,12 +119,15 @@ class XVLAModel(nn.Module):
|
||||
return torch.float32
|
||||
|
||||
def _apply_dtype(self) -> None:
|
||||
"""Apply dtype casting to model components based on config."""
|
||||
"""
|
||||
Apply dtype casting to model components based on config.
|
||||
"""
|
||||
target_dtype = self._get_target_dtype()
|
||||
self.to(dtype=target_dtype)
|
||||
|
||||
def _apply_freezing(self) -> None:
|
||||
"""Freeze VLM vision and language encoders based on config options.
|
||||
"""
|
||||
Freeze VLM vision and language encoders based on config options.
|
||||
Keep only policy transformer and soft prompts trainable.
|
||||
"""
|
||||
# Freeze vision encoder
|
||||
@@ -159,7 +164,9 @@ class XVLAModel(nn.Module):
|
||||
pixel_values: torch.FloatTensor,
|
||||
image_mask: torch.Tensor,
|
||||
) -> dict[str, torch.Tensor]:
|
||||
"""Encode text and multi-view images via Florence2 encoder."""
|
||||
"""
|
||||
Encode text and multi-view images via Florence2 encoder.
|
||||
"""
|
||||
batch_size, num_views = pixel_values.shape[:2]
|
||||
flat_mask = image_mask.view(-1).to(dtype=torch.bool)
|
||||
flat_images = pixel_values.flatten(0, 1)
|
||||
@@ -197,7 +204,9 @@ class XVLAModel(nn.Module):
|
||||
proprio: torch.Tensor,
|
||||
action: torch.Tensor,
|
||||
) -> dict[str, torch.Tensor]:
|
||||
"""Forward pass for the XVLA model."""
|
||||
"""
|
||||
Forward pass for the XVLA model.
|
||||
"""
|
||||
target_dtype = self._get_target_dtype()
|
||||
image_input = image_input.to(dtype=target_dtype)
|
||||
proprio = proprio.to(dtype=target_dtype)
|
||||
@@ -269,12 +278,6 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
name = "xvla"
|
||||
|
||||
def __init__(self, config: XVLAConfig, **kwargs):
|
||||
"""Build the Florence-2-backed XVLA model from `config`.
|
||||
|
||||
Args:
|
||||
config (XVLAConfig): The policy configuration.
|
||||
kwargs: Forwarded to the base class.
|
||||
"""
|
||||
require_package("transformers", extra="xvla")
|
||||
super().__init__(config)
|
||||
config.validate_features()
|
||||
@@ -284,19 +287,16 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
self.reset()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.reset`]. Reinitializes the action queue used by
|
||||
`select_action`.
|
||||
"""
|
||||
self._queues = {
|
||||
ACTION: deque(maxlen=self.config.n_action_steps),
|
||||
}
|
||||
|
||||
def get_optim_params(self) -> dict:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.get_optim_params`].
|
||||
"""Return trainable named parameters for optimization.
|
||||
|
||||
Returns a `{name: param}` dict of every parameter with `requires_grad=True`, keyed by its full
|
||||
parameter name so the XVLA AdamW optimizer preset can apply differential learning rates (e.g. a
|
||||
reduced learning rate for VLM parameters) by name pattern.
|
||||
Returns a dict of name -> param for all trainable parameters.
|
||||
This enables the xvla-adamw optimizer to apply differential learning rates
|
||||
based on parameter names (e.g., 1/10 LR for VLM components).
|
||||
"""
|
||||
return dict(filter(lambda kv: kv[1].requires_grad, self.named_parameters()))
|
||||
|
||||
@@ -389,15 +389,6 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
}
|
||||
|
||||
def forward(self, batch: dict[str, Tensor]) -> tuple[Tensor, dict]:
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.forward`].
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation/action tensors.
|
||||
|
||||
Returns:
|
||||
tuple[Tensor, dict]: The total loss, summed over the action space's per-term losses, and a
|
||||
dict of the individual loss terms for logging.
|
||||
"""
|
||||
inputs = self._build_model_inputs(batch)
|
||||
targets = self._prepare_action_targets(batch)
|
||||
losses = self.model(action=targets, **inputs)
|
||||
@@ -414,28 +405,12 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_action_chunk(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor: # noqa: ARG002
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.predict_action_chunk`].
|
||||
|
||||
Args:
|
||||
batch (dict[str, Tensor]): A batch of preprocessed, normalized observation tensors.
|
||||
noise (Tensor | None, *optional*): Accepted for interface compatibility with
|
||||
`ActionSelectKwargs`; currently unused.
|
||||
|
||||
Returns:
|
||||
Tensor: The predicted action chunk, generated with `config.num_denoising_steps` steps of the
|
||||
flow-matching action head.
|
||||
"""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
return self._get_action_chunk(batch)
|
||||
|
||||
@torch.no_grad()
|
||||
def select_action(self, batch: dict[str, Tensor], noise: Tensor | None = None) -> Tensor: # noqa: ARG002
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.select_action`].
|
||||
|
||||
Uses an action queue populated by `predict_action_chunk`: the queue is refilled with the first
|
||||
`n_action_steps` predicted actions whenever it runs empty.
|
||||
"""
|
||||
self.eval()
|
||||
self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
|
||||
|
||||
@@ -461,11 +436,10 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
strict: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
"""See [`~policies.pretrained.PreTrainedPolicy.from_pretrained`].
|
||||
|
||||
Loads `model.safetensors` directly, remapping checkpoints saved with the old vendored
|
||||
Florence-2 module layout to the native `transformers` layout when detected, and restoring
|
||||
whichever alias of the tied encoder/shared token embedding `safetensors` deduplicated on save.
|
||||
"""
|
||||
Loads XVLA model weights with:
|
||||
- automatic prefix 'model.' added to all keys
|
||||
- skip list for layers that should remain randomly initialized
|
||||
"""
|
||||
import safetensors.torch
|
||||
|
||||
@@ -541,8 +515,7 @@ class XVLAPolicy(PreTrainedPolicy):
|
||||
|
||||
def _is_vendored_florence_state_dict(state_dict: dict[str, Tensor], prefix: str = "model.vlm.") -> bool:
|
||||
"""Detect XVLA checkpoints saved with the old vendored (Microsoft remote-code) Florence-2
|
||||
module layout by their signature keys.
|
||||
"""
|
||||
module layout by their signature keys."""
|
||||
return f"{prefix}image_projection" in state_dict or any(
|
||||
key.startswith(f"{prefix}language_model.model.") for key in state_dict
|
||||
)
|
||||
|
||||
@@ -50,7 +50,10 @@ def make_xvla_pre_post_processors(
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Build the LeRobot processor pipelines for XVLA."""
|
||||
"""
|
||||
Build the LeRobot processor pipelines for XVLA.
|
||||
"""
|
||||
|
||||
steps = make_default_policy_processor_steps(config, dataset_stats)
|
||||
|
||||
input_steps = [
|
||||
@@ -79,7 +82,8 @@ def make_xvla_pre_post_processors(
|
||||
# Custom XVLA processor steps
|
||||
@dataclass
|
||||
class LiberoProcessorStep(ObservationProcessorStep):
|
||||
"""Processes LIBERO observations into the LeRobot format.
|
||||
"""
|
||||
Processes LIBERO observations into the LeRobot format.
|
||||
|
||||
This step handles the specific observation structure from LIBERO environments,
|
||||
which includes nested robot_state dictionaries and image observations.
|
||||
@@ -99,7 +103,9 @@ class LiberoProcessorStep(ObservationProcessorStep):
|
||||
"""
|
||||
|
||||
def _process_observation(self, observation):
|
||||
"""Processes both image and robot_state observations from LIBERO."""
|
||||
"""
|
||||
Processes both image and robot_state observations from LIBERO.
|
||||
"""
|
||||
processed_obs = observation.copy()
|
||||
for key in list(processed_obs.keys()):
|
||||
if key.startswith(f"{OBS_IMAGES}."):
|
||||
@@ -135,7 +141,9 @@ class LiberoProcessorStep(ObservationProcessorStep):
|
||||
def transform_features(
|
||||
self, features: dict[PipelineFeatureType, dict[str, PolicyFeature]]
|
||||
) -> dict[PipelineFeatureType, dict[str, PolicyFeature]]:
|
||||
"""Transforms feature keys from the LIBERO format to the LeRobot standard."""
|
||||
"""
|
||||
Transforms feature keys from the LIBERO format to the LeRobot standard.
|
||||
"""
|
||||
new_features: dict[PipelineFeatureType, dict[str, PolicyFeature]] = {}
|
||||
|
||||
# copy over non-STATE features
|
||||
@@ -158,7 +166,8 @@ class LiberoProcessorStep(ObservationProcessorStep):
|
||||
return new_features
|
||||
|
||||
def _mat_to_rotate6d(self, rot_mats: torch.Tensor) -> torch.Tensor:
|
||||
"""Convert batched rotation matrices (B, 3, 3) into 6D rotation representation (B, 6).
|
||||
"""
|
||||
Convert batched rotation matrices (B, 3, 3) into 6D rotation representation (B, 6).
|
||||
|
||||
Args:
|
||||
rot_mats (Tensor): Rotation matrices of shape (B, 3, 3)
|
||||
@@ -170,6 +179,7 @@ class LiberoProcessorStep(ObservationProcessorStep):
|
||||
TypeError: if input is not a torch tensor
|
||||
ValueError: if shape is not (B, 3, 3)
|
||||
"""
|
||||
|
||||
if not isinstance(rot_mats, torch.Tensor):
|
||||
raise TypeError(f"mat_to_rot6d expects a torch.Tensor, got {type(rot_mats)}")
|
||||
|
||||
@@ -198,7 +208,8 @@ class XVLAImageScaleProcessorStep(ProcessorStep):
|
||||
for XVLA models that expect images in uint8-like range.
|
||||
|
||||
Args:
|
||||
image_keys (`list[str] | None`, *optional*): Observation keys to apply this to. `None` applies to all image observations.
|
||||
image_keys: List of observation keys that contain images to scale.
|
||||
If None, will automatically detect keys starting with "observation.images."
|
||||
"""
|
||||
|
||||
image_keys: list[str] | None = None
|
||||
@@ -248,8 +259,9 @@ class XVLAImageToFloatProcessorStep(ProcessorStep):
|
||||
that are stored as uint8 values.
|
||||
|
||||
Args:
|
||||
image_keys (`list[str] | None`, *optional*): Observation keys to apply this to. `None` applies to all image observations.
|
||||
validate_range (`bool`, *optional*, defaults to `True`): Whether to check that input values are already in [0, 255] before converting.
|
||||
image_keys: List of observation keys that contain images to convert.
|
||||
If None, will automatically detect keys starting with "observation.images."
|
||||
validate_range: If True, validates that input values are in [0, 255] range (default: True)
|
||||
|
||||
Raises:
|
||||
ValueError: If validate_range is True and image values are not in [0, 255] range.
|
||||
@@ -322,7 +334,8 @@ class XVLAImageNetNormalizeProcessorStep(ProcessorStep):
|
||||
The normalization formula is: (image - mean) / std
|
||||
|
||||
Args:
|
||||
image_keys (`list[str] | None`, *optional*): Observation keys to apply this to. `None` applies to all image observations.
|
||||
image_keys: List of observation keys that contain images to normalize.
|
||||
If None, will automatically detect keys starting with "observation.images."
|
||||
|
||||
Raises:
|
||||
ValueError: If image values are not in the [0, 1] range.
|
||||
@@ -396,7 +409,7 @@ class XVLAAddDomainIdProcessorStep(ProcessorStep):
|
||||
which is used by XVLA to identify different robot embodiments or task domains.
|
||||
|
||||
Args:
|
||||
domain_id (`int`, *optional*, defaults to 0): The domain ID to add.
|
||||
domain_id: The domain ID to add (default: 3)
|
||||
"""
|
||||
|
||||
domain_id: int = 0
|
||||
@@ -447,8 +460,7 @@ class XVLARotation6DToAxisAngleProcessorStep(ProcessorStep):
|
||||
Final output: [target_eef (3), axis_angle (3), gripper (1)] = 7D action
|
||||
|
||||
Args:
|
||||
expected_action_dim (`int`, *optional*, defaults to 10): Expected input action dimension (supports
|
||||
6D rotation + extras).
|
||||
expected_action_dim: Expected input action dimension (default: 10, supports 6D rotation + extras)
|
||||
"""
|
||||
|
||||
expected_action_dim: int = 10
|
||||
@@ -504,7 +516,9 @@ def make_xvla_libero_pre_post_processors() -> tuple[
|
||||
PolicyProcessorPipeline[dict[str, Any], dict[str, Any]],
|
||||
PolicyProcessorPipeline[PolicyAction, PolicyAction],
|
||||
]:
|
||||
"""Build the LeRobot processor pipelines for XVLA with LIBERO environment."""
|
||||
"""
|
||||
Build the LeRobot processor pipelines for XVLA with LIBERO environment.
|
||||
"""
|
||||
pre_processor_steps: list[ProcessorStep] = []
|
||||
post_processor_steps: list[ProcessorStep] = []
|
||||
pre_processor_steps.extend(
|
||||
|
||||
@@ -217,12 +217,10 @@ class AddBatchDimensionProcessorStep(ProcessorStep):
|
||||
This step combines individual processors for actions, observations, and complementary data
|
||||
to create a batched transition (batch size 1) from a single-instance transition.
|
||||
|
||||
**Attributes**:
|
||||
- **to_batch_action_processor** (`AddBatchDimensionActionStep`) -- Processor for the action component.
|
||||
- **to_batch_observation_processor** (`AddBatchDimensionObservationStep`) -- Processor for the
|
||||
observation component.
|
||||
- **to_batch_complementary_data_processor** (`AddBatchDimensionComplementaryDataStep`) -- Processor
|
||||
for the complementary data component.
|
||||
Attributes:
|
||||
to_batch_action_processor: Processor for the action component.
|
||||
to_batch_observation_processor: Processor for the observation component.
|
||||
to_batch_complementary_data_processor: Processor for the complementary data component.
|
||||
"""
|
||||
|
||||
to_batch_action_processor: AddBatchDimensionActionStep = field(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user