Skip to content

Repository files navigation

SmolVLA × ALOHA × xArm — Cross-Dataset Reproduction

Reproducing SmolVLA on 16 LeRobot v3 datasets spanning four robot platforms — bimanual ALOHA (sim + real), single-arm xArm, and the 2-DOF PushT planar pusher — all on a Mac (Apple MPS, no NVIDIA GPU).

Companion repo to SmolVLA_cl (LIBERO + EEG modality experiments). Where the cl repo asks "can we add a new modality?", this repo asks "how well does SmolVLA generalise across embodiments and data distributions?"

Trained weights

All 16 SmolVLA checkpoints and the 5 RT-1 checkpoints are published on HuggingFace:

Note the model cards are more precise than this README on one point: these runs use train_expert_only=False, so only the vision tower is frozen — the VLM text tower trains too. "SmolVLM2 frozen" below should be read as "frozen vision encoder".


Key takeaways — what the whole project says

Architecture > Scale > Data Quantity. At small-data behavior-cloning scale (50–800 episodes), the choice of action head (continuous flow-matching vs. discrete 256-bin tokens) and the choice of pretrained backbone matter more than the parameter count. A 243M-parameter RT-1 beats a 450M-parameter SmolVLA on 3 of 5 benchmarked tasks.

Seven concrete lessons distilled from 16 SmolVLA runs + 5 RT-1 runs on the same Mac MPS:

  1. Real-robot data is no harder than sim — Top-4 real ALOHA datasets (ziploc 0.556, battery 0.651, cups 0.694, coffee 0.802) outperform sim ALOHA (insertion 0.716, transfer 0.729). The sim-to-real gap is overhyped for behavior cloning.

  2. Architecture choice flips with task difficulty — RT-1's discrete actions win by −19% to −43% on stereotyped tasks (transfer, battery, push), but lose by +43% on hard ones (xarm_lift). SmolVLA's pretrained SmolVLM2 backbone is the deciding factor when tasks are hard, not the action head.

  3. Scripted demos overfit harder than humanaloha_insertion_scripted reaches train loss 0.040 (vs human 0.118) but evaluates worse (L2 0.864 vs 0.716). Human inconsistency acts as regularization.

  4. Image resolution is a silent bottleneckxarm_push (84×84, no gripper) is the best dataset overall (L2 0.216); xarm_lift (84×84, with gripper) is one of the worst (L2 1.324). Same image, vastly different difficulty — 84×84 can't time the gripper's open/close transitions.

  5. Multi-task pays a ~2× penalty at small dataaloha_multitask L2=1.33 vs ~0.72 single-task. Shared representations need >50 episodes per task to break even.

  6. Normalization mismatch is a real bugaloha_multitask_scripted failed catastrophically (loss 9–55) because two scripted datasets had std-of-joint-0 differing by 40×. Always verify per-dataset stats compatibility before joint training.

  7. The gripper-binary problem is architecture-independent — Both RT-1 and SmolVLA fail on the right-arm gripper dim (MAE 0.13–0.27 for both). This is a data-side bottleneck — open/close transitions are 1% of frames — and no amount of model capacity fixes it.

Methodological win: All 21 model trainings ran on a single M5 MacBook (no NVIDIA GPU, ~20h total compute). VLA research has a lower entry barrier than commonly assumed.


TL;DR

Rank Dataset L2 ↓ MAE ↓ Notes
🥇 xarm_push 0.216 0.105 Simplest task — 3-DOF position, no gripper
🥈 xarm_push_replay 0.529 0.269 Replay variant
🥉 aloha_static_ziploc_slide ★REAL 0.556 0.106 Best real-robot result — fine manipulation
4 aloha_static_battery ★REAL 0.651 0.121 Real-robot, battery insertion
5 aloha_static_cups_open ★REAL 0.694 0.139 Real-robot, cup opening
6 aloha_insertion 0.716 0.154 Sim, human demos
7 aloha_transfer 0.729 0.149 Sim, human demos
8 aloha_static_coffee ★REAL 0.802 0.168 Real-robot
9 aloha_insertion_scripted 0.864 0.186 Low train loss (0.040), higher L2
10 xarm_lift_replay 1.042 0.448 84×84 imgs, grip struggles
11 aloha_transfer_scripted 1.156 0.214 Scripted
12 xarm_lift 1.324 0.565 ❌ 84×84 resolution bottleneck
13 aloha_multitask 1.327 0.276 2-task human, shared repr. penalty
14 aloha_static_towel ★REAL 1.479 0.250 ❌ Deformable object, hardest real-robot
15 aloha_multitask_scripted 1.531 0.282 ⚠️ Normalization mismatch — see note
pusht 40.4 px 25.7 px ‡ 2-DOF pixel space, not comparable

pusht L2 is in pixel units (512×512 canvas); 40 px ≈ 8% of image width per step — reasonable for flow matching.
⚠️ aloha_multitask_scripted training loss (~9–55) signals a normalization failure: transfer_scripted has std[joint0]=0.004 (joint locked) while insertion_scripted has std[joint0]=0.161 — applying transfer stats to insertion data inflates z-scores by 40×. Eval result not meaningful for comparison.

Full cross-dataset comparison


What this shows

Seven empirical findings, each backed by at least one pair of runs:

Finding Evidence
Real-robot data is no harder than sim Top-4 real-robot datasets (ziploc 0.556, battery 0.651, cups 0.694) all beat sim ALOHA (0.716–0.729)
Fine manipulation generalises well aloha_static_ziploc_slide L2=0.556 is #3 overall despite precise fine-grained contact
Deformable objects are harder aloha_static_towel L2=1.479 is the hardest real-robot run — deformable cloth requires reasoning about visual state that doesn't follow rigid-body heuristics
Scripted demos overfit differently aloha_insertion_scripted: 3× lower train loss (0.040) but higher open-loop L2 (0.864 vs 0.716) than human demos
Image resolution matters for grasping xarm_lift (84×84) L2=1.32 vs xarm_push (no gripper) L2=0.22 — same image size, vastly different difficulty
Multi-task pays ~2× penalty aloha_multitask L2=1.33 vs single-task ~0.72, even at 5000 steps
Scripted multi-task fails via normalization mismatch aloha_multitask_scripted loss 9–55 (vs 0.456 for human multitask) — scripted policies lock joints differently per task, making cross-dataset z-scoring degenerate

The 16 datasets

All datasets are in LeRobot v3 format (Parquet files + MP4 videos), freely available on HuggingFace under the lerobot/ organisation.

ALOHA bimanual robot — 14-dim joint space, 480×640 overhead camera

Group Dataset HF Repo Episodes
Sim, human demos aloha_transfer lerobot/aloha_sim_transfer_cube_human 50
Sim, human demos aloha_insertion lerobot/aloha_sim_insertion_human 50
Sim, human demos aloha_multitask both of the above 100
Sim, scripted aloha_transfer_scripted lerobot/aloha_sim_transfer_cube_scripted 50
Sim, scripted aloha_insertion_scripted lerobot/aloha_sim_insertion_scripted 50
Sim, scripted aloha_multitask_scripted both scripted datasets 100
★ Real robot aloha_static_coffee lerobot/aloha_static_coffee ~50
★ Real robot aloha_static_battery lerobot/aloha_static_battery ~50
★ Real robot aloha_static_cups_open lerobot/aloha_static_cups_open 50
★ Real robot (deformable) aloha_static_towel lerobot/aloha_static_towel 50
★ Real robot (fine) aloha_static_ziploc_slide lerobot/aloha_static_ziploc_slide 56

xArm — single-arm robot, 84×84 images, 3-4 DOF action

Group Dataset HF Repo Action dim
Lift task xarm_lift lerobot/xarm_lift_medium 4 (x,y,z,gripper)
Push task xarm_push lerobot/xarm_push_medium 3 (x,y,z)
Lift replay xarm_lift_replay lerobot/xarm_lift_medium_replay 4
Push replay xarm_push_replay lerobot/xarm_push_medium_replay 3

PushT — 2-DOF planar pusher, 96×96 images

Dataset HF Repo Episodes Action dim Units
pusht lerobot/pusht 206 2 (x,y pixel) pixels (0–512)

Full configs live in dataset_configs.py.


Input / Output layout

ALOHA Static (real robot) — aloha_static_coffee, aloha_static_battery, aloha_static_cups_open, aloha_static_towel, aloha_static_ziploc_slide

INPUT
  observation.images.cam_high   (3, 480, 640)   ← overhead RGB, normalized to [0,1]
  observation.state             (14,)           ← 14-dim joint positions, z-score normalized
  OBS_LANGUAGE_TOKENS           (48,)           ← tokenized task description
                                                  e.g. "Place the coffee capsule inside..."

OUTPUT
  action chunk                  (50, 14)        ← future 1 second of joint targets @ 50 FPS
                                                  z-score normalized; ×std+mean at inference

xArm — xarm_lift, xarm_push

INPUT
  observation.image             (3, 84, 84)     ← single camera
  observation.state             (4,)            ← (x, y, z, gripper)
  OBS_LANGUAGE_TOKENS           (48,)           ← e.g. "Lift the red cube"

OUTPUT
  action chunk                  (10, 4 or 3)    ← future 0.67 s @ 15 FPS

PushT — pusht

INPUT
  observation.image             (3, 96, 96)     ← top-down camera, pixel coords
  observation.state             (2,)            ← agent (x, y) in pixels
  OBS_LANGUAGE_TOKENS           (48,)           ← "Push the T-shaped block onto the target."

OUTPUT
  action chunk                  (10, 2)         ← next 1 s @ 10 FPS, pixel coordinates
                                                  Note: L2 metric is in pixels, not radians

Internal SmolVLA processing

image → SmolVLM2 vision encoder (resize to 512×512, frozen)
                        │
                        ▼
language tokens → SmolVLM2 transformer → context tokens
                        │
state ──────────────────┼──→ Flow-matching action expert (~100M params, trained)
                        ▼
                10-step denoising → action chunk

Quickstart

cd /Users/r/Projects/SmolVLA_ALOHA

# All scripts use the lerobot conda env:
PY=/opt/anaconda3/envs/lerobot/bin/python

# ── 1) Train one dataset (auto-downloads to ./Data on first run) ──────────────
DATASET=aloha_transfer       TRAIN_STEPS=3000 BATCH_SIZE=4 $PY train_generic.py
DATASET=aloha_static_coffee  TRAIN_STEPS=3000 BATCH_SIZE=4 $PY train_generic.py  # real-robot
DATASET=xarm_push            TRAIN_STEPS=3000 BATCH_SIZE=8 $PY train_generic.py
DATASET=pusht                TRAIN_STEPS=3000 BATCH_SIZE=16 $PY train_generic.py
DATASET=aloha_multitask      TRAIN_STEPS=5000 BATCH_SIZE=4 $PY train_generic.py

# ── 2) Open-loop evaluation (no simulator needed) ────────────────────────────
DATASET=aloha_transfer       STEP=3000 N_EVAL_EP=10 $PY eval_generic.py
DATASET=aloha_static_coffee  STEP=3000 N_EVAL_EP=10 $PY eval_generic.py
DATASET=pusht                STEP=3000 N_EVAL_EP=20 $PY eval_generic.py

# ── 3) Cross-dataset comparison plot (once all evals are done) ───────────────
$PY plot_comparison.py

Any dataset key in dataset_configs.py is valid. To add a new LeRobot v3 dataset, append an entry to that registry and the generic train/eval scripts will handle it automatically.


Files

File Purpose
dataset_configs.py Registry of 17 dataset configurations — image keys, state/action dims, chunk size, FPS, train_steps, batch_size. Includes raw_gt flag for datasets that store raw (non-z-scored) parquet values (e.g. pusht pixel coordinates).
train_generic.py Generic trainer for any dataset in the registry — handles parquet loading, MP4 video decoding by global frame index, normalization stats, action-chunking, and flow-matching loss
eval_generic.py Open-loop evaluation: feeds real observations frame-by-frame, calls model.select_action() each step, computes per-dim MAE + overall L2 in original units; saves eval_{dataset}_step{step}.npz and a per-dim bar chart
plot_comparison.py Reads all eval_*.npz and produces the 4-panel cross-dataset figure (L2 bar, MAE bar, normalised heatmap, loss curves)
download_data.py Stand-alone HuggingFace dataset downloader (rarely needed — training auto-downloads)
aloha_config.py Legacy ALOHA-specific config — superseded by the registry in dataset_configs.py but kept for reference
train_smolvla_aloha.py Legacy ALOHA-only trainer; superseded by train_generic.py
eval_smolvla_aloha.py Legacy ALOHA-only evaluator; superseded by eval_generic.py

Environment

Apple MPS on M-series Mac; reuses the lerobot conda env from the parent project:

# Same env as SmolVLA_cl
PYTHONPATH is NOT required for this repo (we don't depend on LIBERO).

Dependencies are inherited from a local lerobot source install at /Users/r/lerobot/src (set via sys.path.insert in train_generic.py). If you don't have that, swap it for a pip install lerobot and adjust the path.

pip install lerobot transformers torch pandas pyarrow opencv-python matplotlib huggingface_hub

Performance on M5 MPS

SmolVLA uses action chunking — one VLM forward pass generates 10–50 actions, executed one per step.

Run Steps Time Final loss
aloha_transfer (sim, human) 3000 ~28 min 0.118
aloha_insertion_scripted 3000 ~30 min 0.040
aloha_transfer_scripted 3000 ~30 min 0.060
aloha_multitask 5000 ~50 min 0.456
aloha_static_coffee ★REAL 3000 ~35 min 0.110
aloha_static_battery ★REAL 3000 ~33 min 0.115
xarm_lift 3000 ~20 min 1.502
xarm_push 3000 ~47 min 0.300
xarm_lift_replay 3000 ~48 min 1.513
xarm_push_replay 3000 ~48 min 1.457
pusht 3000 ~85 min 0.239
aloha_static_cups_open ★REAL 3000 ~33 min
aloha_static_towel ★REAL 3000 ~25 min
aloha_static_ziploc_slide ★REAL 3000 ~25 min
aloha_multitask_scripted 3000 ~25 min ~9 ⚠️

(Full eval results in eval_output/; training checkpoints not committed — ~70 GB total across all runs.)


Why these results, in one paragraph each

xarm_push wins (L2=0.216) — 3-DOF pure positional control with no gripper. The simplest task in the lineup. SmolVLA's flow-matching head fits it almost perfectly.

aloha_static_ziploc_slide is the best real-robot result (L2=0.556) — Despite involving precise fine-grained contact with a flexible ziploc bag, this task turns out to be highly repeatable: the robot starts in a consistent configuration, the bag location is predictable, and the motion is a clean slide gesture. SmolVLA handles it better than any other real-robot task.

Real-robot data is not harder than sim — The top-4 real-robot datasets (ziploc 0.556, battery 0.651, cups 0.694, coffee 0.802) all outperform or match the sim ALOHA tasks (insertion 0.716, transfer 0.729). SmolVLA's frozen vision encoder handles real lighting and textures without degradation.

Deformable objects are the hardest real-robot task (L2=1.479)aloha_static_towel is the worst-performing real-robot run. A towel's shape changes throughout the episode, creating visual states that the model cannot easily map to joint-space actions. This is the clearest embodiment-specific bottleneck found.

Scripted demos: lower train loss, higher open-loop L2aloha_insertion_scripted reaches loss 0.040 (3× lower than human 0.118) because scripted trajectories are highly consistent. But the model memorises those exact trajectories and fails to generalise to the slight variations in held-out episodes, giving worse open-loop L2 (0.864 vs 0.716).

xarm_lift fails (L2=1.32) — 84×84 images are too low-resolution to time the gripper open/close action precisely. The position trajectory is fine but the binary gripper signal is consistently mistimed. Confirmed by xarm_push (same 84×84 images, no gripper) being the best result in the entire benchmark.

aloha_multitask pays a ~2× penalty (L2=1.33 vs ~0.72 single-task) — Sharing one set of weights between transfer-cube and peg-insertion produces a representation that's mediocre at both. Even doubling the training budget to 5000 steps doesn't fully recover.

aloha_multitask_scripted fails via normalization mismatch — The scripted transfer task locks certain joints (std[joint0]=0.004), while the scripted insertion task moves those same joints widely (std[joint0]=0.161). Normalising both datasets with the transfer statistics inflates insertion z-scores by 40×, making the loss unreliable (~9–55 vs 0.456 for human multitask). This is a practical lesson: scripted multi-task data requires per-dataset normalisation.

pusht (L2=40 pixels, ~8% of canvas) — The 2-DOF PushT pusher uses pixel coordinates (0–512) instead of radians, so L2 is not comparable to ALOHA/xArm. 40 pixels per step on a 512×512 canvas corresponds to ~8% per-step error — reasonable given the model saw only 206 episodes for 3000 steps. Pusht serves as a reference for the simplest possible continuous control task with visual input.


Reproducing the full 16-dataset run

DATASETS=(aloha_transfer aloha_insertion aloha_multitask
          aloha_transfer_scripted aloha_insertion_scripted aloha_multitask_scripted
          aloha_static_coffee aloha_static_battery
          aloha_static_cups_open aloha_static_towel aloha_static_ziploc_slide
          xarm_lift xarm_push xarm_lift_replay xarm_push_replay
          pusht)

for d in "${DATASETS[@]}"; do
    DATASET=$d $PY train_generic.py
    DATASET=$d STEP=3000 $PY eval_generic.py
done
$PY plot_comparison.py

Training auto-downloads any missing dataset on first run. Data/ is not committed (~4 GB) and checkpoints/ is not committed (~70 GB) — both are listed in .gitignore.


RT-1 vs SmolVLA head-to-head (RT1_repro/)

A second arm of this project reproduces Google's RT-1 (Robotics Transformer, 2022) on the same datasets and the same Mac MPS hardware, using the lucidrains PyTorch port. This isolates the architecture effect: same data, same compute, two different action heads.

Aspect SmolVLA RT-1 (this port)
Params 450 M 243 M (CLIP frozen)
Backbone SmolVLM2 frozen + flow-matching expert MaxViT-base + Token Learner + 6-layer Transformer
Action head Continuous flow-matching, 10-step denoising Discrete, 256 bins per dim, cross-entropy
History 1 frame 6 frames
Image size 512×512 (resize with padding) 224×224
Training 3000 steps, BS=4–16 2000 steps, BS=4

Final results (all 5 datasets complete)

Dataset SmolVLA L2 RT-1 L2 Δ Winner
aloha_transfer 0.729 0.589 −19% RT-1
aloha_insertion 0.716 0.744 +4% SmolVLA (tied)
aloha_static_battery ★REAL 0.651 0.394 −40% RT-1 ★★
xarm_push (3-DOF, no gripper) 0.216 0.123 −43% RT-1 ★★★
xarm_lift (4-DOF, with gripper) 1.324 1.890 +43% SmolVLA ★★

Score: 3–2 RT-1. But the split is not random — it follows a clean rule:

  • RT-1 wins when actions are continuous & stereotyped (transfer, battery, push)
  • SmolVLA wins when (a) actions need sub-bin precision near contact (insertion) or (b) the task is hard enough that the pretrained VLM backbone gives real headroom (lift)

RT-1 vs SmolVLA

Four findings

1. RT-1 dominates on stereotyped continuous control (up to −43%). On aloha_static_battery, RT-1's per-dim MAE for the left arm (dims 0–7) is 0.006–0.078, while SmolVLA's is 0.025–0.274 — up to 10× lower. On xarm_push (no gripper), RT-1's per-dim MAE is flat 0.055–0.065 vs SmolVLA's 0.064–0.159. Discrete action tokens (256 bins) recover the highly stereotyped joint trajectories of real demos with much less precision loss than continuous flow-matching. The action space "rounds to the nearest bin" exactly when the demonstrator's hand was tremor-stable.

2. Discrete actions fail on hard tasks (+43% worse on xarm_lift). This is the surprising flip: on xarm_lift, RT-1's per-dim MAE is 0.70 / 0.83 / 0.81 / 0.80 vs SmolVLA's 0.53 / 0.58 / 0.59 / 0.56 — RT-1 is uniformly 30–40% worse on every dim, not just the gripper. With 2000 steps and no pretrained vision-language backbone, RT-1 cannot learn xarm_lift's tighter dynamics (84×84 images + binary gripper + larger z-range). SmolVLA's frozen SmolVLM2 backbone provides robust visual representations even on tiny images. The 207M-parameter gap matters most when the task is genuinely hard.

3. SmolVLA wins narrowly on precision-critical contact (+4% on insertion). Peg insertion needs sub-bin angular precision near the contact point. The 256-bin quantization cost shows up here, but only as a 4% tie — noise level.

4. The gripper-binary problem is universal. Both models' worst dim on every ALOHA task is the right-arm gripper (dim 13): RT-1 MAE 0.13–0.24, SmolVLA MAE 0.14–0.27. Gripper is open/close with very few transition frames; both action heads struggle equally. This is a data-side bottleneck (sparse class-imbalanced labels), not architecture-side.

What this tells us

Regime Architecture choice
Easy continuous task, small data RT-1 / discrete actions win (-19% to -43%)
Precision contact in sim SmolVLA wins barely (+4%)
Hard task with low-res images SmolVLA / pretrained VLM wins (+43%)
Binary signals (gripper) Both struggle equally — data problem

Two simultaneous trends are visible:

  • Action-head trade-off: discrete bins cost quantization but save inductive bias; continuous flow-matching pays no quantization tax but needs more data to denoise.
  • Backbone-capacity trade-off: SmolVLM2's 450M frozen params are not "wasted" — they buy robustness on hard tasks (xarm_lift) even when the action head is identical.

For a Mac-scale reproduction (~50 episodes per task), the right architecture depends on task difficulty:

  • Predictable, stereotyped behaviour → use RT-1 (smaller, faster, often better).
  • Hard or out-of-distribution behaviour → use SmolVLA-style pretrained backbone for headroom.

Combined model size: 243 M (RT-1) + 450 M (SmolVLA) = 693 M params trained on the same Mac MPS, no NVIDIA GPU.

Reproducing the RT-1 arm

cd /Users/r/Projects/SmolVLA_ALOHA
pip install robotic-transformer-pytorch tiktoken sentencepiece

# Train + eval on any LeRobot v3 dataset already in dataset_configs.py
DATASET=aloha_static_battery TRAIN_STEPS=2000 $PY RT1_repro/train_rt1.py
DATASET=aloha_static_battery STEP=2000 N_EVAL_EP=8 $PY RT1_repro/eval_rt1.py

# Generate head-to-head plot
$PY RT1_repro/compare_rt1_vs_smolvla.py

The RT-1 trainer reuses SmolVLA's dataset_configs.py registry and LeRobotDataset helpers — adding new datasets to one project adds them to the other automatically.


Related projects

SmolVLA_cl — SmolVLA + LIBERO + EEG as a fourth modality. Where this repo studies cross-embodiment generalisation, the cl repo studies whether a brain signal (EEG motor imagery) can be added as a controllable input. Both run on the same hardware (Apple MPS) and share the same SmolVLA architecture; only the embodiments and modalities differ.

Released weights and data: Twu31/smolvla-cross-embodiment-mps · Twu31/rt1-lerobot-mps · Twu31/smolvla-libero-eeg · Twu31/so101_hand_blue_napkin

About

SmolVLA reproduction across 11 LeRobot v3 datasets — ALOHA (sim + real), xArm, on Apple MPS

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages