Navigation

Introduction to AI

Machine Learning

Deep Learning

Generative AI

Tools & Frameworks

General

Vision-Language-Action Models

Vision-Language-Action (VLA) models represent a fundamental shift in robot learning: rather than training separate perception, language understanding, and control modules, VLA models are single end-to-end transformers that map camera images and natural language instructions directly to robot actions. They inherit the semantic knowledge, object understanding, and common-sense reasoning from internet-scale vision-language pre-training, applying it to physical manipulation tasks.

The Robotics Generalization Problem

Traditional robot learning suffers from brittle generalization: a policy trained to pick up a red apple fails when presented with a green apple, a slightly different table, or the same task phrased differently. Each new variation requires new demonstrations. This is because standard imitation learning policies learn pixel-to-action mappings with no semantic understanding of the objects involved.

The insight behind VLAs: pre-trained vision-language models (VLMs) already understand that “apple,” “fruit,” “red object on table,” and “grasp the produce” refer to the same thing. If robot actions can be generated by the same network, that semantic generalization transfers to physical manipulation.

RT-2: Robotics Transformer 2

RT-2 (Brohan et al., Google DeepMind, 2023) was the first large-scale demonstration of this approach. The key insight: treat robot actions as tokens in the language model’s vocabulary.

Continuous robot actions (end-effector position deltas, gripper state) are discretized into 256 bins each and represented as special tokens appended to the model’s vocabulary. The VLM (PaLI-X 55B or PaLM-E) is then fine-tuned on robot demonstration data in a multi-task setup where it generates action tokens after the visual and language context:

from transformers import AutoProcessor, AutoModelForVision2Seq
import torch
import numpy as np

# ── Action tokenization ────────────────────────────────────────────────────

N_BINS = 256       # discretization resolution
ACTION_DIMS = 7    # 6-DOF end-effector delta + 1 gripper

def continuous_to_action_tokens(
    action: np.ndarray,
    action_min: np.ndarray,
    action_max: np.ndarray,
    vocab_offset: int = 32000   # place action tokens after the text vocabulary
) -> list[int]:
    """
    Convert continuous robot action to discrete vocabulary tokens.
    
    RT-2 discretizes each action dimension independently into N_BINS bins.
    Each bin index becomes a distinct token ID in the extended vocabulary.
    
    action: shape (7,) — [dx, dy, dz, droll, dpitch, dyaw, gripper_open]
    Returns: list of 7 token IDs
    """
    # Normalize to [0, 1] using per-dimension statistics from training data
    normalized = (action - action_min) / (action_max - action_min + 1e-8)
    normalized = np.clip(normalized, 0.0, 1.0)
    
    # Quantize to integer bins
    bin_indices = (normalized * (N_BINS - 1)).astype(int)
    
    # Map to token IDs
    token_ids = [vocab_offset + i * N_BINS + bin_idx
                 for i, bin_idx in enumerate(bin_indices)]
    
    return token_ids


def action_tokens_to_continuous(
    token_ids: list[int],
    action_min: np.ndarray,
    action_max: np.ndarray,
    vocab_offset: int = 32000
) -> np.ndarray:
    """Inverse of continuous_to_action_tokens: decode action tokens to floats."""
    bin_indices = np.array([
        (tok - vocab_offset - i * N_BINS) for i, tok in enumerate(token_ids)
    ])
    
    normalized = bin_indices / (N_BINS - 1)
    action = normalized * (action_max - action_min) + action_min
    return action


# ── RT-2 style inference pipeline ─────────────────────────────────────────

class RT2StyleInference:
    """
    Demonstrates the inference loop for an RT-2 style VLA model.
    
    The model receives:
    - Image observation (RGB, 320×256 typically)
    - Natural language instruction ("pick up the cup and place it on the plate")
    
    And outputs:
    - 7 action tokens → decoded to continuous robot action
    
    In production, this runs at ~1-3 Hz on a mobile robot.
    """
    
    def __init__(self, model_name: str = "openvla/openvla-7b"):
        self.processor = AutoProcessor.from_pretrained(
            model_name, trust_remote_code=True
        )
        self.model = AutoModelForVision2Seq.from_pretrained(
            model_name,
            torch_dtype=torch.bfloat16,
            device_map="auto",
            trust_remote_code=True
        )
        self.model.eval()
    
    def predict_action(
        self,
        image: "PIL.Image",
        instruction: str
    ) -> np.ndarray:
        """
        Predict robot action from image and language instruction.
        
        Returns: action array shape (7,) — [dx, dy, dz, droll, dpitch, dyaw, gripper]
        values normalized to [-1, 1] for each dimension.
        """
        # OpenVLA uses a specific prompt format
        prompt = f"In: What action should the robot take to {instruction}?\nOut:"
        
        inputs = self.processor(prompt, image).to(
            self.model.device, dtype=torch.bfloat16
        )
        
        # Generate 7 action tokens autoregressively
        with torch.no_grad():
            action_tokens = self.model.predict_action(
                **inputs,
                unnorm_key="bridge_orig",   # dataset normalization statistics
                do_sample=False             # deterministic for control
            )
        
        return action_tokens   # shape (7,) decoded continuous actions

π0: Flow Matching for Dexterous Manipulation

π0 (Physical Intelligence, 2024) takes a different approach: instead of discretizing actions into language tokens, it uses a flow matching diffusion head on top of a VLM backbone. This enables smooth, high-frequency action generation better suited to dexterous tasks (folding laundry, assembling objects).

The architecture separates:

  • VLM trunk (PaliGemma 3B): encodes image + language into rich semantic features
  • Action expert (a smaller transformer): receives VLM features and denoises action chunks via flow matching
import torch
import torch.nn as nn

class FlowMatchingActionHead(nn.Module):
    """
    π0-style flow matching action generation head.
    
    Generates robot actions by learning a flow (vector field) that
    transforms Gaussian noise into the action distribution conditioned
    on VLM visual-language features.
    
    Advantages over token-based actions (RT-2 style):
    - Continuous action space — no quantization artifacts
    - Natural handling of multi-modal action distributions (e.g., pick left OR right)
    - Better suited for high-frequency, dexterous manipulation
    - Action chunks: predict next T=16 timesteps jointly for temporal consistency
    """
    
    def __init__(
        self,
        vlm_feature_dim: int = 2048,    # PaliGemma output dimension
        action_dim: int = 7,             # robot DoF
        action_horizon: int = 16,        # predict 16 future steps at once
        n_denoising_steps: int = 10,     # flow matching inference steps
        hidden_dim: int = 512
    ):
        super().__init__()
        
        self.action_dim = action_dim
        self.action_horizon = action_horizon
        self.n_steps = n_denoising_steps
        
        # Sinusoidal time embedding for denoising step conditioning
        self.time_embed = nn.Sequential(
            nn.Linear(64, hidden_dim),
            nn.SiLU(),
            nn.Linear(hidden_dim, hidden_dim)
        )
        
        # Cross-attention to VLM features
        self.cross_attn = nn.MultiheadAttention(
            embed_dim=hidden_dim,
            kdim=vlm_feature_dim,
            vdim=vlm_feature_dim,
            num_heads=8,
            batch_first=True
        )
        
        # Action denoising network: maps noisy actions + conditioning → velocity field
        action_flat_dim = action_dim * action_horizon
        self.denoiser = nn.Sequential(
            nn.Linear(action_flat_dim + hidden_dim, hidden_dim),
            nn.SiLU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.SiLU(),
            nn.Linear(hidden_dim, action_flat_dim)
        )

    def forward(
        self,
        noisy_actions: torch.Tensor,    # (B, T, action_dim) — noisy action chunk
        vlm_features: torch.Tensor,     # (B, seq_len, vlm_feature_dim)
        t: torch.Tensor                  # (B,) — denoising timestep in [0, 1]
    ) -> torch.Tensor:
        """Predict velocity field (dx/dt) for flow matching."""
        B = noisy_actions.shape[0]
        
        # Condition on time
        t_emb = self.time_embed(self._sinusoidal_embed(t))   # (B, hidden_dim)
        
        # Attend to VLM semantic features
        query = t_emb.unsqueeze(1)   # (B, 1, hidden_dim)
        context, _ = self.cross_attn(query, vlm_features, vlm_features)
        context = context.squeeze(1)  # (B, hidden_dim)
        
        # Flatten noisy actions and concatenate with context
        actions_flat = noisy_actions.reshape(B, -1)   # (B, T * action_dim)
        combined = torch.cat([actions_flat, context], dim=-1)
        
        # Predict velocity field
        velocity_flat = self.denoiser(combined)   # (B, T * action_dim)
        return velocity_flat.reshape(B, self.action_horizon, self.action_dim)

    def _sinusoidal_embed(self, t: torch.Tensor, dim: int = 64) -> torch.Tensor:
        half = dim // 2
        freqs = torch.exp(
            -torch.arange(half, device=t.device) * (torch.log(torch.tensor(10000.0)) / (half - 1))
        )
        emb = t.unsqueeze(-1) * freqs.unsqueeze(0)
        return torch.cat([emb.sin(), emb.cos()], dim=-1)

    @torch.no_grad()
    def sample_action(
        self,
        vlm_features: torch.Tensor   # (B, seq_len, vlm_feature_dim)
    ) -> torch.Tensor:
        """Generate action chunk via flow matching (ODE integration)."""
        B = vlm_features.shape[0]
        
        # Start from Gaussian noise
        x = torch.randn(
            B, self.action_horizon, self.action_dim, device=vlm_features.device
        )
        
        # Euler integration of the learned flow from t=0 (noise) to t=1 (data)
        dt = 1.0 / self.n_steps
        for i in range(self.n_steps):
            t = torch.full((B,), i / self.n_steps, device=vlm_features.device)
            velocity = self.forward(x, vlm_features, t)
            x = x + dt * velocity
        
        return x   # (B, T, action_dim) — predicted action chunk

Co-Training and Data Diversity

A critical factor in VLA generalization is co-training: jointly training on robot demonstration data and internet-scale vision-language data (image captioning, VQA, visual reasoning). This prevents catastrophic forgetting of the VLM’s pre-trained visual understanding and grounds the robot actions in the same semantic space as natural language.

The Open X-Embodiment dataset (Austin et al., 2023) aggregates 22 robot datasets across 22 robot types — enabling a single VLA to control multiple physical embodiments.

Evaluation Benchmarks

BenchmarkDescriptionRT-2OpenVLA
BridgeData V2Kitchen manipulation, 13 tasks62%56.7%
LIBERO (spatial)Table manipulation with spatial instructions84.7%
LIBERO (semantic)Tasks requiring object-property reasoning80.8%
Unseen objectsGeneralization to novel objects3× over BC baselinesimilar

Practical Considerations

Inference latency: Large VLAs (55B RT-2) require multi-GPU setups to meet real-time control requirements (~10 Hz). Smaller variants like OpenVLA (7B) run at usable frequencies on a single A100. Distillation and quantization are active research areas.

Action representation tradeoffs:

  • Discrete tokens (RT-2): leverages language model sampling, easy multi-modal distributions, but quantization limits precision for fine manipulation
  • Diffusion/flow heads (π0): continuous and expressive, better for dexterous tasks, but slower inference
  • Regression heads (many baselines): fast, but unimodal — struggles with tasks where multiple valid actions exist

Safety: VLAs inherit LLM failure modes — they can be confused by adversarial prompts, generate physically impossible action sequences, or fail unpredictably on distribution-shifted images. Current deployments rely on supervisory teleoperation and action validation layers.