Accelerating VLA Fine-Tuning: Implementing OFT (Optimized Fine-Tuning) for OpenVLA

16 min read · Published Apr 20, 2026, 6:03 AM

Standard Vision-Language-Action models inherit the inference architecture of large language models—token-by-token autoregressive generation. That design choice, acceptable for text tasks, is a structural impediment in robotics. This article documents the full implementation path for the OFT (Optimized Fine-Tuning) recipe, covering MLP action head replacement, L1 regression objective design, proprioceptive normalization, and distributed training configuration on ALOHA hardware.


The State of VLA Inference: Breaking Through the Latency Wall

Sequential autoregressive decoding in VLAs produces latencies exceeding 200ms per action step. At that rate, closed-loop robotic control at 50–100Hz is impossible. The robot's physical state evolves faster than the policy can respond, causing accumulated positional error and instability.

Technical Warning: "Sequential decoding is the primary bottleneck for real-time VLA robotics deployment due to the token-by-token generation overhead inherent in standard LLM architectures." This is not a quantization problem or a batch-size problem—it is architectural.

The source of the bottleneck: OpenVLA's base architecture uses a Prismatic VLM backbone that discretizes continuous actions into tokens and decodes them sequentially. For a 7-DoF arm with a gripper (8 action dimensions), this means 8 sequential decode passes through the full transformer stack before a single motor command is issued.

sequenceDiagram
    participant Cam as Camera Input
    participant VLM as VLM Backbone (Prismatic)
    participant Dec as Autoregressive Decoder
    participant Buf as Action Buffer
    participant Ctrl as Robot Controller

    Cam->>VLM: Image + Language Tokens
    VLM->>Dec: Context Embedding
    Dec->>Dec: Decode Token 1 (Joint 1)
    Dec->>Dec: Decode Token 2 (Joint 2)
    Dec->>Dec: Decode Token 3 (Joint 3)
    Dec->>Dec: Decode Token 4 (Joint 4)
    Dec->>Dec: Decode Token 5 (Joint 5)
    Dec->>Dec: Decode Token 6 (Joint 6)
    Dec->>Dec: Decode Token 7 (Joint 7)
    Dec->>Dec: Decode Token 8 (Gripper)
    Dec->>Buf: Complete Action Vector (>200ms elapsed)
    Buf->>Ctrl: Issue Motor Command
    Note over Ctrl: Robot state has already drifted

Each decode step is causally dependent on the previous token, preventing parallelism. Hardware utilization during these sequential steps is low—the GPU waits on memory reads between passes. The result is high wall-clock latency despite available compute headroom.

Python 3.10+ and PyTorch 2.4+ are prerequisites before any optimization work begins; older runtimes lack the CUDA graph capture APIs that make parallel decoding tractable.


Architecting the OFT Recipe for OpenVLA

OFT does not patch the sequential decoder—it replaces the decoding paradigm entirely. The recipe combines three mechanisms: parallel decoding, action chunking, and continuous action representation with L1 regression. Together, these achieve a 25–50x increase in inference throughput over standard sequential OpenVLA models, with a 20%+ increase in success rates across manipulation tasks.

The throughput gain is architectural, not incidental. Parallel decoding collapses the 8 sequential decode passes into a single forward pass through an MLP action head attached to the final hidden state of the VLM backbone. Action chunking then amortizes that single forward pass cost across N future timesteps, further reducing per-step overhead.

Property Standard Transformer Decoding OFT Parallel Decoding
Action generation passes 8 (sequential) 1 (parallel MLP)
Latency per action step >200ms ~4–8ms
Throughput scaling O(N) with action dims O(1) with action dims
GPU utilization pattern Stalled between tokens Single dense forward pass
Action representation Discrete tokens Continuous float32
Chunk support None N-step horizon buffer
Control frequency achievable <5Hz 50–100Hz

Action chunking requires synchronization buffers defined within the Prismatic library configuration. When the model predicts a chunk of N actions at once, the control loop reads from a FIFO buffer, consuming one action per tick while the model runs asynchronously to replenish the buffer.

"OFT integrates parallel decoding, action chunking, and L1 regression to significantly enhance inference efficiency and policy performance." — ArXiv:2502.19645


Replacing the Action Head: Designing the Custom MLP Projector

The Prismatic backbone produces a latent embedding of dimension 4096. The default OpenVLA architecture projects this through a language model head into a token vocabulary. OFT replaces that final projection with a lightweight MLP that maps 4096 → action space dimension directly, outputting continuous float32 values.

For an ALOHA robot arm with 7-DoF joint control plus gripper state, the output dimension is 8. For a chunk size of N, the output dimension becomes 8×N.

Technical Warning: Prismatic requires specific weight initialization for custom MLP heads. Xavier uniform initialization on the projection layers, with the final output layer initialized to near-zero weights, prevents feature destabilization at the start of fine-tuning.

import torch
import torch.nn as nn
from typing import Optional

class OFTActionHead(nn.Module):
    """
    Replaces the LM head in Prismatic-based VLAs.
    Maps backbone hidden states to continuous action chunks.
    """

    def __init__(
        self,
        input_dim: int = 4096,       # Prismatic backbone latent dim
        action_dim: int = 8,          # 7-DoF joints + gripper
        chunk_size: int = 10,         # N future timesteps per forward pass
        hidden_dim: int = 1024,       # Intermediate projection width
        dropout_rate: float = 0.1,
    ):
        super().__init__()
        self.action_dim = action_dim
        self.chunk_size = chunk_size
        output_dim = action_dim * chunk_size  # Flat output, reshaped at inference

        self.projector = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.SiLU(),                # SiLU avoids dead neuron problem vs ReLU
            nn.Dropout(dropout_rate),
            nn.Linear(hidden_dim, hidden_dim // 2),
            nn.SiLU(),
            nn.Linear(hidden_dim // 2, output_dim),
        )
        self._initialize_weights()

    def _initialize_weights(self) -> None:
        for module in self.projector[:-1]:
            if isinstance(module, nn.Linear):
                # Xavier init preserves gradient variance through projection layers
                nn.init.xavier_uniform_(module.weight)
                nn.init.zeros_(module.bias)
        # Near-zero init on final layer prevents large initial action predictions
        nn.init.normal_(self.projector[-1].weight, mean=0.0, std=1e-3)
        nn.init.zeros_(self.projector[-1].bias)

    def forward(self, hidden_state: torch.Tensor) -> torch.Tensor:
        """
        Args:
            hidden_state: [batch, seq_len, input_dim] — take last token position
        Returns:
            action_chunk: [batch, chunk_size, action_dim]
        """
        last_hidden = hidden_state[:, -1, :]  # Extract final token embedding
        flat_actions = self.projector(last_hidden)
        return flat_actions.view(-1, self.chunk_size, self.action_dim)


def attach_oft_head_to_openvla(
    backbone: nn.Module,
    action_dim: int = 8,
    chunk_size: int = 10,
) -> nn.Module:
    """
    Detaches the LM head from a Prismatic VLA and attaches OFTActionHead.
    Freezes backbone vision encoder; fine-tunes LLM layers + action head.
    """
    # Disable the existing language model head
    backbone.language_model.lm_head.requires_grad_(False)

    # Attach OFT head as a top-level attribute for optimizer grouping
    backbone.oft_action_head = OFTActionHead(
        input_dim=backbone.language_model.config.hidden_size,
        action_dim=action_dim,
        chunk_size=chunk_size,
    )

    # Freeze vision tower; fine-tune LLM backbone + new action head only
    for param in backbone.vision_backbone.parameters():
        param.requires_grad = False

    return backbone

This architecture eliminates the vocabulary softmax computation and all autoregressive decode iterations. The single MLP forward pass over the final hidden state takes microseconds, not hundreds of milliseconds.


L1 Regression Objective: Stabilizing Continuous Action Spaces

L1 regression provides robustness against outlier trajectories in imitation learning. MSE penalizes large deviations quadratically—a single aberrant teleoperation trajectory produces a loss spike that corrupts gradient updates for nearby, correct trajectories. L1's linear penalty naturally down-weights those outliers.

The gradient behavior explains the difference precisely. For a predicted action $\hat{a}$ and ground truth $a^*$:

$$\mathcal{L}{\text{MSE}} = \frac{1}{N}\sum_i - a_i^}^{N}(\hat{a)^2 \quad \Rightarrow \quad \frac{\partial \mathcal{L}_{\text{MSE}}}{\partial \hat{a}_i} = \frac{2}{N}(\hat{a}_i - a_i^)$$

$$\mathcal{L}{\text{L1}} = \frac{1}{N}\sum_i - a_i^}^{N}|\hat{a| \quad \Rightarrow \quad \frac{\partial \mathcal{L}_{\text{L1}}}{\partial \hat{a}_i} = \frac{1}{N}\text{sign}(\hat{a}_i - a_i^)$$

The L1 gradient is constant-magnitude regardless of error size. In sparse action distributions—where a robot holds position for multiple timesteps before executing a motion—MSE aggressively penalizes the small but real velocity outputs at those near-zero states, suppressing necessary motion initiation. L1's sign-based gradient applies uniform pressure, maintaining the ability to exit near-zero action regions.

Technical Warning: The L1 objective's constant gradient creates convergence oscillation near zero-action states during the final training stages. Use a learning rate scheduler with cosine decay and a minimum LR of 1e-6 to prevent bouncing across the zero crossing.

def oft_action_loss(
    predicted_chunk: torch.Tensor,   # [batch, chunk_size, action_dim]
    target_chunk: torch.Tensor,      # [batch, chunk_size, action_dim]
    reduction: str = "mean",
) -> torch.Tensor:
    """
    L1 loss over action chunks. Masks padding timesteps if chunk is truncated.
    """
    # Huber loss with delta=1.0 provides L1 behavior for large errors,
    # smooth L2 near zero — best of both regimes
    loss = nn.functional.huber_loss(
        predicted_chunk,
        target_chunk,
        delta=1.0,
        reduction=reduction,
    )
    return loss

Pro-Tip: Pure L1 (nn.L1Loss) is theoretically correct, but Huber loss (delta=1.0) provides L1 behavior for large residuals and removes the gradient discontinuity at zero that causes oscillation. Use Huber in production.


The Engineering Barrier: Proprioceptive State Normalization

Cross-robot fine-tuning fails most often here, not in the model architecture. Joint angle ranges, end-effector coordinate frames, and velocity scales differ across embodiments. A model trained on ALOHA's joint space (±π radians) fed raw telemetry from a Franka arm (different kinematic limits) will saturate its input projectors within the first inference step.

Z-score normalization using statistics derived from each robot's specific operational range prevents saturation. These statistics must be computed from the deployment hardware's actual telemetry—not approximated from the training dataset's global statistics.

Technical Warning: Normalization must be applied to end-effector telemetry vectors before ingestion by the VLA projector. Applying it after the projector is a silent failure mode: the model receives unnormalized inputs but normalized gradients during fine-tuning, creating a distribution mismatch at inference.

import numpy as np
import logging
from dataclasses import dataclass, field
from typing import Optional

logger = logging.getLogger("oft.proprioception")


@dataclass
class ProprioceptiveNormalizer:
    """
    Computes and applies Z-score normalization to robot state vectors.
    Must be fit on operational telemetry from the target hardware.
    """
    joint_dim: int = 7
    ee_dim: int = 6          # 3D position + 3D orientation (Euler or axis-angle)
    gripper_dim: int = 1
    _mean: Optional[np.ndarray] = field(default=None, repr=False)
    _std: Optional[np.ndarray] = field(default=None, repr=False)
    _eps: float = 1e-6       # Prevent division by zero on locked joints

    @property
    def state_dim(self) -> int:
        return self.joint_dim + self.ee_dim + self.gripper_dim

    def fit(self, telemetry_buffer: np.ndarray) -> "ProprioceptiveNormalizer":
        """
        Fit normalizer on recorded operational telemetry.
        Args:
            telemetry_buffer: [T, state_dim] array of raw hardware readings
        """
        assert telemetry_buffer.shape[1] == self.state_dim, (
            f"Expected state_dim={self.state_dim}, got {telemetry_buffer.shape[1]}"
        )
        self._mean = telemetry_buffer.mean(axis=0)
        self._std = telemetry_buffer.std(axis=0)

        # Log any zero-variance dimensions — indicates a stuck joint or fixed axis
        zero_var_dims = np.where(self._std < self._eps)[0]
        if len(zero_var_dims) > 0:
            logger.warning(
                "Zero variance detected in state dims %s — possible hardware fault "
                "or locked joint. Substituting std=1.0 to prevent inf normalization.",
                zero_var_dims.tolist(),
            )
            self._std[zero_var_dims] = 1.0

        return self

    def normalize(self, state: np.ndarray) -> np.ndarray:
        """Apply Z-score normalization. Raises if not yet fit."""
        if self._mean is None or self._std is None:
            raise RuntimeError("Normalizer must be fit before calling normalize().")
        return (state - self._mean) / (self._std + self._eps)

    def denormalize(self, normalized_state: np.ndarray) -> np.ndarray:
        """Invert normalization — required when converting model output to motor commands."""
        if self._mean is None or self._std is None:
            raise RuntimeError("Normalizer must be fit before calling denormalize().")
        return normalized_state * (self._std + self._eps) + self._mean

    def save(self, path: str) -> None:
        np.savez(path, mean=self._mean, std=self._std)
        logger.info("Normalizer statistics saved to %s", path)

    @classmethod
    def load(cls, path: str, **kwargs) -> "ProprioceptiveNormalizer":
        data = np.load(path)
        instance = cls(**kwargs)
        instance._mean = data["mean"]
        instance._std = data["std"]
        return instance

Persist the fitted normalizer alongside model checkpoints. A checkpoint without its normalizer statistics is non-deployable—the model will receive a different input distribution than it was trained on.


Implementing the Training Loop on ALOHA Hardware

Fine-tuning OpenVLA with OFT requires a minimum of 18GB VRAM per GPU. This threshold accommodates the Prismatic backbone (~14GB in bfloat16), the OFT action head gradients, parallel decoding buffers, and a viable batch size. Sub-18GB GPUs require either aggressive gradient checkpointing or batch size 1, which degrades normalization statistics in the data pipeline.

ALOHA hardware integration uses PyTorch Distributed Data Parallel (DDP) for multi-device training. The following script configures a two-GPU DDP run with the OFT training loop:

#!/bin/bash
# oft_train_aloha.sh — DDP training for OpenVLA-OFT on ALOHA hardware
# Requires: PyTorch 2.4+, CUDA 12.1+, Prismatic library installed

set -euo pipefail

# Hardware configuration — adjust GPU IDs to match your topology
CUDA_VISIBLE_DEVICES=0,1
NPROC_PER_NODE=2
MASTER_PORT=29500

# Training configuration
CONFIG_PATH="configs/oft_aloha.yaml"
OUTPUT_DIR="checkpoints/openvla_oft_$(date +%Y%m%d_%H%M%S)"
DATASET_PATH="/data/aloha/telemetry_lerobot_format"
NORMALIZER_PATH="/data/aloha/normalizer_stats.npz"

mkdir -p "${OUTPUT_DIR}"

# Launch DDP training via torchrun (PyTorch 2.4+ elastic launcher)
torchrun \
    --standalone \
    --nproc_per_node="${NPROC_PER_NODE}" \
    --master_port="${MASTER_PORT}" \
    train_oft.py \
    --config "${CONFIG_PATH}" \
    --output_dir "${OUTPUT_DIR}" \
    --dataset_path "${DATASET_PATH}" \
    --normalizer_path "${NORMALIZER_PATH}" \
    --report_to "wandb" \
    2>&1 | tee "${OUTPUT_DIR}/train.log"

echo "Training complete. Checkpoints written to ${OUTPUT_DIR}"

Memory Constraint: At batch size 8 with chunk size 10 and bfloat16 precision, peak VRAM reaches ~17.2GB on a single A100 40GB. On RTX 3090/4090 (24GB), this is feasible. On A6000 (48GB), increase batch size to 16 for better GPU utilization.


Memory Efficiency: Strategies for High-Throughput Fine-Tuning

Gradient checkpointing in the Prismatic library reduces peak VRAM consumption by 30–40% at the cost of approximately 15% additional compute time per step—a worthwhile trade-off when operating at the 18GB boundary.

# configs/oft_aloha.yaml — OFT training configuration for Prismatic-based OpenVLA

model:
  backbone: "openvla-7b"              # Prismatic VLA base model identifier
  freeze_vision_backbone: true         # Only fine-tune LLM layers + action head
  action_head:
    hidden_dim: 1024
    chunk_size: 10
    action_dim: 8                      # 7-DoF joints + gripper
    dropout: 0.1

training:
  gradient_checkpointing: true         # Required to stay within 18GB VRAM limit
  mixed_precision: "bf16"              # bfloat16 — stable on Ampere/Ada architectures
  learning_rate: 2.0e-5
  lr_scheduler: "cosine"
  warmup_steps: 200
  max_steps: 50000
  batch_size: 8
  gradient_accumulation_steps: 4      # Effective batch = 32 across both GPUs
  save_every_n_steps: 2000
  eval_every_n_steps: 500

loss:
  type: "huber"
  delta: 1.0

data:
  chunk_size: 10
  action_dim: 8
  use_proprio: true
  proprio_normalizer_path: "auto"     # Loaded from --normalizer_path flag

optimizer:
  type: "adamw"
  weight_decay: 0.01
  betas: [0.9, 0.999]

ddp:
  find_unused_parameters: false        # Action head uses all backbone outputs
  gradient_as_bucket_view: true        # Reduces DDP communication buffer allocation

Pro-Tip: Set gradient_as_bucket_view: true in DDP configuration. This eliminates a secondary gradient buffer copy, recovering ~1.5GB VRAM on the 18GB boundary—often the difference between a run that fits and one that OOMs on the first backward pass.


Observability and Debugging: Ensuring Closed-Loop Success

Action drift is the primary failure mode in deployed OFT policies. It occurs when the normalized predicted action sequence diverges from the actual robot state—caused by normalization mismatch, stale chunk consumption, or out-of-distribution visual inputs. Drift must be detected in real-time, not post-hoc from logs.

Trigger drift detection when the L1 error between normalized predicted actions and hardware telemetry states exceeds 5%. At 50–100Hz control frequency, logging must match the control loop rate.

import logging
import time
import numpy as np
from typing import Deque
from collections import deque

logger = logging.getLogger("oft.drift_monitor")
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s.%(msecs)03d [%(levelname)s] %(name)s: %(message)s",
    datefmt="%H:%M:%S",
)


class ActionDriftMonitor:
    """
    Real-time drift detection between predicted and observed proprioceptive state.
    Operates at control loop frequency (50-100Hz).
    """

    DRIFT_THRESHOLD: float = 0.05       # 5% L1 error triggers warning
    CRITICAL_THRESHOLD: float = 0.15    # 15% triggers emergency stop signal

    def __init__(self, action_dim: int = 8, history_len: int = 50):
        self.action_dim = action_dim
        # Rolling window of drift magnitudes for trend detection
        self.drift_history: Deque[float] = deque(maxlen=history_len)
        self._step = 0
        self._last_log_time = time.monotonic()

    def check(
        self,
        predicted_action: np.ndarray,    # Normalized, [action_dim]
        observed_state: np.ndarray,       # Normalized, [action_dim]
    ) -> bool:
        """
        Returns True if drift exceeds critical threshold (caller should halt execution).
        """
        assert predicted_action.shape == (self.action_dim,), (
            f"predicted_action shape mismatch: {predicted_action.shape}"
        )
        assert observed_state.shape == (self.action_dim,), (
            f"observed_state shape mismatch: {observed_state.shape}"
        )

        # Per-dimension L1 error — catch joint-specific drift early
        per_dim_error = np.abs(predicted_action - observed_state)
        mean_drift = per_dim_error.mean()
        max_dim_idx = int(per_dim_error.argmax())
        self.drift_history.append(float(mean_drift))
        self._step += 1

        now = time.monotonic()
        # Log at 10Hz regardless of control loop rate to avoid I/O saturation
        if now - self._last_log_time >= 0.1:
            trend = np.mean(list(self.drift_history))
            logger.debug(
                "step=%d | mean_drift=%.4f | max_dim=%d (err=%.4f) | trend=%.4f",
                self._step, mean_drift, max_dim_idx,
                per_dim_error[max_dim_idx], trend,
            )
            self._last_log_time = now

        if mean_drift > self.CRITICAL_THRESHOLD:
            logger.critical(
                "CRITICAL DRIFT at step %d: %.4f > %.4f — issuing halt signal. "
                "Check normalizer statistics and visual input distribution.",
                self._step, mean_drift, self.CRITICAL_THRESHOLD,
            )
            return True  # Caller must stop robot motion

        if mean_drift > self.DRIFT_THRESHOLD:
            logger.warning(
                "Drift threshold exceeded at step %d: %.4f > %.4f (dim %d worst).",
                self._step, mean_drift, self.DRIFT_THRESHOLD, max_dim_idx,
            )

        return False

Benchmarking Improvements: From Simulation to Real-World

OpenVLA-OFT achieves a 97.1% success rate on the LIBERO benchmark—a result that validates the combined effect of parallel decoding and L1 regression across diverse manipulation tasks. That number requires context: LIBERO tests spatial, object, goal, and long-horizon task categories, each exercising different aspects of policy generalization. A 97.1% aggregate masks per-category variance; engineers should decompose results by task category before claiming parity.

"Our empirical analysis informs an Optimized Fine-Tuning (OFT) recipe... to altogether improve inference efficiency, policy performance, and flexibility in the model's input-output specifications." — ArXiv:2502.19645

The 25–50x throughput gain is achieved by eliminating sequential decode passes. A baseline OpenVLA model generating 8 action tokens sequentially at 25ms per token produces one action vector per 200ms. The OFT MLP head generates a 10-step action chunk in a single forward pass taking ~4–8ms, yielding 10 actions per 4–8ms—roughly 1250–2500 actions per second versus the baseline's 5.

To benchmark this rigorously on your hardware:

Benchmark protocol: - Run baseline sequential OpenVLA and OFT model on identical GPU hardware (do not mix A100 and RTX results) - Measure wall-clock time from image receipt to first motor command issued - Report P50, P95, and P99 latencies—not means, which hide tail latency spikes that cause control loop misses - Measure success rate on a fixed held-out task set (minimum 50 rollouts per condition) in both sim and real

Chart: Latency vs. Success Rate Tradeoff - X-axis: Action generation latency (ms), log scale from 4ms to 300ms - Y-axis: Task success rate (%) - Baseline OpenVLA: ~210ms latency, ~76% success - OFT (chunk=1): ~7ms latency, ~88% success - OFT (chunk=10): ~8ms latency, ~97% success (LIBERO) - OFT (chunk=20): ~10ms latency, ~94% success (diminishing returns from stale chunks)

The chunk=20 degradation is instructive: longer chunks improve throughput marginally but increase the probability that early-chunk predictions are stale by the time they execute, particularly in contact-rich tasks.

Pro-Tip: Benchmark with torch.compile(model, mode="reduce-overhead") enabled. PyTorch 2.4's compiler eliminates Python interpreter overhead in the MLP forward pass, yielding an additional 15–20% latency reduction at inference.


Future-Proofing Robotics with Optimized VLA Workflows

Scaling OFT workflows beyond single-embodiment fine-tuning currently requires high-performance clusters with 80GB VRAM GPUs for large-scale multi-embodiment training. The architectural pattern—frozen vision backbone, fine-tuned LLM layers, swappable MLP action head—is designed for embodiment-specific head swapping without full model retraining. This positions OFT as a deployment-time adaptation pattern, not just a training-time optimization.

The trajectory for VLA robotics fine-tuning runs toward per-embodiment adapter modules (LoRA applied to the LLM layers) combined with shared vision and language representations. OFT's MLP head already separates embodiment-specific logic from the shared backbone, making it structurally compatible with future adapter-based multi-robot deployment.

Hardware Prerequisites Checklist for OFT Deployment:

  • [ ] GPU VRAM: 18GB minimum per device for single-GPU fine-tuning; 80GB recommended for multi-embodiment or large-batch training
  • [ ] GPU Architecture: CUDA-capable, Ampere or newer (bfloat16 support required)
  • [ ] CPU RAM: 64GB minimum for dataset preprocessing and DataLoader workers
  • [ ] Storage: NVMe SSD with >2GB/s sequential read for large-scale VLA dataset ingestion; SATA SSD causes DataLoader bottlenecks at batch_size ≥ 8
  • [ ] Network: CUDA-compatible NICs with RDMA support (InfiniBand or RoCE) for multi-node DDP runs; 10GbE minimum for single-node multi-GPU
  • [ ] Python Runtime: 3.10+
  • [ ] PyTorch: 2.4+ (required for CUDA graph capture and torch.compile)
  • [ ] Prismatic Library: Installed with all VLM backbone dependencies
  • [ ] Normalizer Statistics: Fitted and persisted from target hardware telemetry before training begins
  • [ ] Control Loop Timing: Verified that robot firmware accepts commands at ≥50Hz; OFT's throughput advantage is lost on firmware with coarser command buffers

Keywords: Vision-Language-Action (VLA), OpenVLA, Optimized Fine-Tuning (OFT), Parallel Decoding, Action Chunking, L1 Regression Objective, Proprioceptive State Normalization, ALOHA Robotics Platform, Prismatic Library, Imitation Learning, Sim-to-Real Transfer, Closed-Loop Control, MLP Action Head