Rate this Page

Source code for torchrl.data.vla.schema

# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""Canonical TensorDict schema for Vision-Language-Action (VLA) data.

A VLA trajectory maps one or more camera images, optional proprioceptive
state, and a natural-language instruction to robot actions (typically emitted
as a short *action chunk* of future steps). This module defines the canonical
:class:`~tensordict.utils.NestedKey` layout used by the VLA transforms,
policies and losses so that datasets, models and objectives agree on a single
key convention. The layout mirrors the keys produced by
:class:`~torchrl.data.datasets.OpenXExperienceReplay` and the LeRobot dataset
format::

    TensorDict(
        observation: TensorDict(
            image: {<camera>: uint8/float [*B, T, C, H, W]},  # or a single tensor
            state: float [*B, T, state_dim],                  # proprioception
        ),
        language_instruction: NonTensorData | Text,           # raw or tokenized (per-traj)
        action: float [*B, T, action_dim],                    # raw, per-step
        vla_action: VLAAction(
            chunk: float [*B, T, chunk, action_dim],          # built for training
            tokens: long [*B, T, chunk, action_dim],          # tokenized actions
        ),
        action_is_pad: bool [*B, T, chunk],                   # chunk validity mask
        next: TensorDict(...),                                 # TED layout
    )

Nothing here is mandatory: every VLA component exposes its keys via
``set_keys`` / constructor arguments and these constants are merely the shared
defaults.
"""
from __future__ import annotations

import torch
from tensordict import TensorDictBase
from tensordict.utils import is_non_tensor, NestedKey

__all__ = [
    "OBSERVATION_KEY",
    "IMAGE_KEY",
    "STATE_KEY",
    "INSTRUCTION_KEY",
    "ACTION_KEY",
    "VLA_ACTION_KEY",
    "ACTION_CHUNK_KEY",
    "ACTION_IS_PAD_KEY",
    "ACTION_TOKENS_KEY",
    "validate_vla_tensordict",
]

# -- Observation keys --------------------------------------------------------
#: Root of the observation sub-tensordict.
OBSERVATION_KEY: NestedKey = "observation"
#: Camera image(s). A single tensor, or a sub-tensordict keyed by camera name.
IMAGE_KEY: NestedKey = ("observation", "image")
#: Proprioceptive robot state (joint angles, end-effector pose, ...).
STATE_KEY: NestedKey = ("observation", "state")
#: Natural-language task instruction (raw text or tokenized). Stored at the
#: tensordict root (a per-trajectory field) to match the layout of
#: :class:`~torchrl.data.datasets.OpenXExperienceReplay`.
INSTRUCTION_KEY: NestedKey = "language_instruction"

# -- Action keys -------------------------------------------------------------
#: Raw per-step continuous action ``[*B, T, action_dim]`` (dataset native).
ACTION_KEY: NestedKey = "action"
#: Structured action output container.
VLA_ACTION_KEY: NestedKey = "vla_action"
#: Continuous action chunk ``[*B, T, chunk, action_dim]`` (training target).
ACTION_CHUNK_KEY: NestedKey = (VLA_ACTION_KEY, "chunk")
#: Boolean mask ``[*B, T, chunk]`` marking valid (non-padded) chunk steps.
ACTION_IS_PAD_KEY: NestedKey = "action_is_pad"
#: Discrete action tokens ``[*B, T, chunk, action_dim]`` or ``[*B, T, L]``.
ACTION_TOKENS_KEY: NestedKey = (VLA_ACTION_KEY, "tokens")


[docs] def validate_vla_tensordict( tensordict: TensorDictBase, *, instruction_key: NestedKey = INSTRUCTION_KEY, action_key: NestedKey = ACTION_KEY, image_key: NestedKey = IMAGE_KEY, state_key: NestedKey = STATE_KEY, require_instruction: bool = True, require_action: bool = True, require_perception: bool = True, check_finite: bool = True, raise_on_error: bool = True, ) -> list[str]: """Validate that a tensordict follows the canonical VLA schema. The check is intentionally permissive: it verifies the presence of the keys a VLA pipeline relies on and that action tensors are finite, without constraining shapes beyond what is necessary. Args: tensordict (TensorDictBase): the tensordict to validate. Keyword Args: instruction_key (NestedKey): language-instruction key. Defaults to ``"language_instruction"``. action_key (NestedKey): action key. Defaults to ``"action"``. image_key (NestedKey): image key. Defaults to ``("observation", "image")``. state_key (NestedKey): proprioceptive-state key. Defaults to ``("observation", "state")``. require_instruction (bool): if ``True``, a missing instruction is an error. Defaults to ``True``. require_action (bool): if ``True``, a missing action is an error. Defaults to ``True``. require_perception (bool): if ``True``, at least one of the image or state keys must be present. Defaults to ``True``. check_finite (bool): if ``True``, float action tensors must be finite. Defaults to ``True``. raise_on_error (bool): if ``True`` (default), raise a ``ValueError`` when any issue is found; otherwise return the list of issues. Returns: a list of human-readable issue strings (empty if the tensordict is valid). When ``raise_on_error`` is ``True`` a non-empty list raises a ``ValueError`` instead of being returned. Examples: >>> import torch >>> from tensordict import NonTensorData, TensorDict >>> from torchrl.data.vla import validate_vla_tensordict >>> td = TensorDict( ... { ... "observation": { ... "image": torch.zeros(2, 3, 8, 8, dtype=torch.uint8), ... }, ... "language_instruction": NonTensorData("pick the cube"), ... "action": torch.zeros(2, 7), ... }, ... batch_size=[2], ... ) >>> validate_vla_tensordict(td) [] """ issues: list[str] = [] _missing = object() def _get(key: NestedKey): # ``get`` with a sentinel default resolves nested keys and surfaces # NonTensorData uniformly, unlike ``keys(leaves_only=True)``. A nested # key that descends into a leaf tensor raises ValueError -- treat it as # missing rather than letting the traceback escape. try: return tensordict.get(key, _missing) except (KeyError, TypeError, ValueError): return _missing instruction = _get(instruction_key) if require_instruction and instruction is _missing: issues.append(f"missing language instruction at key {instruction_key!r}") elif instruction is not _missing and is_non_tensor(instruction): data = getattr(instruction, "data", None) if isinstance(data, str) and not data.strip(): issues.append(f"empty language instruction at key {instruction_key!r}") if ( require_perception and _get(image_key) is _missing and _get(state_key) is _missing ): issues.append( f"no perception found: expected an image at {image_key!r} or state " f"at {state_key!r}" ) action = _get(action_key) if require_action and action is _missing: issues.append(f"missing action at key {action_key!r}") elif ( check_finite and isinstance(action, torch.Tensor) and action.is_floating_point() and not torch.isfinite(action).all() ): issues.append(f"non-finite values in action at key {action_key!r}") if issues and raise_on_error: raise ValueError( "Tensordict does not follow the canonical VLA schema:\n - " + "\n - ".join(issues) ) return issues