File size: 9,494 Bytes
1601a2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f818890
 
 
 
 
 
 
 
 
 
 
 
 
 
1601a2f
 
 
 
 
 
 
 
 
 
 
ddd826b
 
804441b
ddd826b
 
 
 
 
 
1601a2f
49cc5f9
 
 
 
 
 
 
 
1601a2f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
---
license: apache-2.0
base_model: Qwen/Qwen3.5-0.8B
tags:
  - robotics
  - vla
  - lerobot
  - libero
  - dual-rate
pipeline_tag: robotics
---

# TinyVLA: a dual-rate VLA that beats OpenVLA-7B on LIBERO-Spatial at 0.64B params

**Headline result: 86% success on LIBERO-Spatial (n=100) in dual-rate mode (semantic latent
refreshed at 1 Hz), 79% full-rate β€” vs OpenVLA-7B's 84.7% β€” with 140M parameters on the
online control path.** Built and trained end-to-end in ~5 days on a single RTX A6000.

## 1. Architecture

```
                SLOW / SMART path (0.5–2 Hz, cached)
instruction ─────────────────────┐
camera β†’ Qwen3.5-0.8B vision tower β†’ Qwen3.5 language stack (first 12 of 24 layers)
                  β”‚                       β”‚
                  β”‚            8 readout tokens β†’ linear β†’ semantic latent (8Γ—512)
                  β”‚                       β”‚ cached between refreshes
──────────────────┼───────────────────────┼──────────────────────────
                  β”‚       FAST / MOTOR path (10–20 Hz, ~140M online)
                  └── 64 spatial tokens per camera ─┐
proprio (padded 16d) ────────────────────────────────
embodiment-id embedding ─────────────────────────────
                                                    β–Ό
                    flow-matching action expert (38M, 12 blocks,
                    interleaved cross-/self-attention, AdaLN-free)
                                                    β–Ό
                              50-step action chunk (padded 8d + validity mask)
```

- **Single shared encoder**: the Qwen3.5-0.8B native ViT (100.6M) feeds both paths.
- **Slow path**: image + instruction + 8 trailing pad-token "readout" slots through the
  truncated language stack (L/2 layer-skip a la SmolVLA); their hidden states β†’ latent 8Γ—512.
- **Fast path**: raw vision-tower spatial tokens (bypassing the LM) + proprio + embodiment
  token, cross-attended by the flow expert. 10 Euler steps at inference.
- **Flow matching**: linear-OT path, t ~ Beta(1.5, 1), velocity target, masked MSE.
- Totals: 643M stored / 39.5M–575M trainable depending on stage / ~140M online per tick.
- Implemented as an **out-of-tree LeRobot policy plugin**
  (`--policy.type=tinyvla --policy.discover_packages_path=tinyvla`, lerobot 0.6.1).

## 2. Training recipe

| Stage | Data | Steps | Trainable | Result |
|---|---|---|---|---|
| 1. Overfit smoke | svla_so100_stacking (56 eps) | 5k | expert+proj (40M) | loss 1.885β†’0.099 |
| 2. Mixture pretrain | 40 SO100/101 community datasets (~10.7K eps, weight 0.65, per-dataset proportional) + Bridge V2 10K eps (0.35) | 40k, bs 64Γ—2, lr 1e-4 | 39.5M (backbone frozen) | held-out RMSE@t0 0.30Οƒ; **stale-latent 2s == fresh** |
| 2b. Staleness aug | same | from step 20k | β€” | latent from frame tβˆ’k, k~U(0,2s), p=0.5 |
| 3. Spatial distillation | + 132K teacher labels (Qwen3.5-4B, center-point β†’ 32Γ—32 grid), CE Γ—0.15 on cam0 tokens | 20k, lr 5e-5 | same + head | head top-1 20.8%, top-5 44.8% (random 0.1%) |
| 4. LIBERO FT (ft4) | HuggingFaceVLA/libero (1693 eps, all suites) | 40k, bs 48, lr 1e-4 / backbone 0.1Γ— | **575M** (LM layers + vision unfrozen; vocab embeddings frozen) | loss 0.066 |

Ablation FTs: ft1 (all frozen) β†’ 0% closed-loop; ft2 (vision unfrozen) β†’ 60%;
ft3 (= ft2 from distilled ckpt) β†’ 61%; **ft4 (LM+vision unfrozen) β†’ 79/86%**.
Unfreezing the LM was the single largest gain; distillation was neutral on LIBERO
(likely overwritten by vision unfreezing during FT β€” bake it into pretraining next time).

Cross-embodiment handling: canonical schema (cam0=scene/cam1=wrist via regex mapping,
per-dataset mean/std normalization, action pad-to-8 + validity mask, state pad-to-16,
learned embodiment-id token). Weighted mixture sampler with per-source retry on corrupt
frames (IndexError/KeyError/decode RuntimeError).

## 3. Results

### LIBERO β€” all four suites (dual-rate, latent @ 1 Hz)

| Suite | TinyVLA | OpenVLA-7B (published) |
|---|---|---|
| Spatial | **86%** (n=100; full-rate 79%) | 84.7% |
| Object | **94%** (n=50) | 88.4% |
| Goal | **86%** (n=50) | 79.2% |
| Long (libero_10) | **70%** (n=50) | 53.7% |
| **Average** | **84.0%** | 76.5% |

Beats OpenVLA-7B on every suite at 1/11 the parameters and ~1/50 the online compute.
XS-VLA (90.3 avg) remains ahead β€” their latent flow matching is future work for us.

### LIBERO-Spatial (100 episodes, 10 per task, init states 0-9)

| Mode | Success |
|---|---|
| Full-rate (latent every chunk) | 79/100 = **79%** |
| **Dual-rate (latent @ 1 Hz)** | 86/100 = **86%** |

Dual-rate β‰₯ full-rate consistently across all evals (also 65% vs 58% at ft3, n=50) β€”
a stable cached latent appears to act as temporal regularization.

### Context (published numbers, their own eval protocols)

| Model | Params | LIBERO-Spatial | 4-suite avg |
|---|---|---|---|
| MolmoAct2-LIBERO | 5B | 98.4% (**97.2% re-measured on our rig**, n=500) | **98.25%** |
| Ο€0 | 3.3B | 98% | β€” |
| SmolVLA-2.25B | 2.25B | 93% | β€” |
| XS-VLA | 0.26B | ~93% | 90.3% |
| MolmoAct-7B (v1) | 7B | 87.0% | 86.6% |
| **TinyVLA (this)** | **0.64B (140M online)** | **86% dual-rate** | **84.0%** |
| OpenVLA | 7B | 84.7% | 76.5% |

**MolmoAct2 context** (from their cards/repo, no numeric tables published outside the
LIBERO-LeRobot card): 5B params on Molmo2-ER backbone + flow expert; claims to beat Ο€0.5;
Molmo2-ER reportedly surpasses GPT-5 / Gemini-Robotics-ER-1.5 on 13 embodied-reasoning
benchmarks; #1 VLA on the MolmoSpace leaderboard. Inference needs ~26GB fp32 (16GB bf16),
cuda-graphs for ~2Γ— speedup β€” H100-class serving vs our Raspberry-Pi-class 140M fast path.
Notably they ship **MolmoAct2-SO100_101** (absolute joint control) β€” a 5B competitor on our
target embodiment; a head-to-head offline eval on SO101 held-out data is an obvious next test.

Honesty notes: our n=100 (Β±8pp) vs their 500; single suite; eval covers init states 0–9
per task (demos of same tasks/init states are in training data β€” this is the standard
LIBERO protocol used by all listed models). Multi-suite evals (object/goal/long) and an
identically-protocoled SmolVLA baseline run are in progress.

### Dual-rate validation (the architectural claim)

- Offline: held-out chunk MSE with latent from a frame 2s earlier == fresh (0.772 vs 0.772).
- Closed-loop: 86% @ 1 Hz refresh vs 79% full-rate.
- Deployment implication (Raspberry Pi 5): the 0.5B slow path can run at 0.5–1 Hz
  (llama.cpp GGUF Q4 territory), only the 140M fast path needs real-time rates.

## 4. Pitfalls we hit (read before reproducing)

1. **robosuite/LIBERO renders are 180Β°-rotated** vs recorded datasets. A vertical flip
   alone leaves a horizontal MIRROR β€” the policy reaches to the wrong side and scores 0%.
   Use `img[::-1, ::-1]`. (lerobot's `LiberoEnv.render()` flips for visualization but
   `_format_raw_obs` feeds policies raw rotated frames β€” check your eval path.)
2. **eef orientation antipodal ambiguity**: env quat β†’ rotvec gives βˆ’Ο€-ish x-component,
   datasets store +Ο€-ish. Canonicalize (flip antipodal representation when rotvec_x < 0)
   or your normalized state input is off by ~17Οƒ.
3. **HuggingFaceVLA/libero meta says fps=10 but frames are native 20 Hz** (~161
   frames/episode β‰ˆ 8s demos). Do NOT action-repeat at eval.
4. **GT-replay harness is the decisive convention test**: replay dataset actions in the env
   (first episode per task ↔ init_state 0). 4/5 success = conventions right; policy-independent.
5. HuggingFaceVLA community_dataset v1/v3 are aggregates of v2.1 sub-datasets β†’ convert with
   lerobot's `convert_dataset_v21_to_v30.py`; 4/44 sources have meta≠parquet row counts
   (broken at source) β€” validate `len(reader.hf_dataset) == meta.total_frames`.
6. Qwen3.5 custom forwards need `mm_token_type_ids` for M-RoPE; patchify must match the
   official processor bit-exact (verify!). Linear-attention needs fla-core + causal-conv1d
   or you get a 5Γ— slower torch fallback.
7. Flow-matching loss can drop while conditioning is ignored β€” A/B with shuffled
   observations to verify the model actually uses them.

## 5. Repository layout

```
src/tinyvla/            # LeRobot plugin: configuration/modeling/processor + modules
scripts/train.py        # accelerate loop: weighted mixture, staleness aug, distill loss, resume
scripts/eval_libero.py  # closed-loop LIBERO eval (all convention fixes, dual-rate, video)
scripts/replay_libero.py, diag_obs_gap.py   # convention diagnostics
scripts/prepare_community_v3.py, label_spatial.py, eval_offline.py
configs/                # stage2/stage3/libero_ft* yaml
checkpoints/            # stage2_final, stage3_final, libero_ft4_final (main)
videos/                 # sample eval episodes
```

## 6. Quick start

```python
from tinyvla.modeling_tinyvla import TinyVLAPolicy
policy = TinyVLAPolicy.from_pretrained("AlexWortega/tinyvla")  # ft4 checkpoint at root
# closed-loop LIBERO: python scripts/eval_libero.py --checkpoint <ckpt> \
#   --suite libero_spatial --episodes 100 --n-action-steps 10 --refresh-s 1.0
```

Trained 2026-08-14 β†’ 2026-08-19 on 1Γ— RTX A6000 (48GB), lerobot 0.6.1, torch 2.10, transformers 5.5.4.