Skip to content

Repository files navigation

EmbodiedVLA-Lab

Adapting and evaluating Vision-Language-Action models on real robot demonstrations.

EmbodiedVLA-Lab is a compact robotics ML engineering study covering the full VLA lifecycle: real-robot demonstration loading, SmolVLA adaptation, held-out action prediction, failure analysis, and reproducible inference.

Pipeline from real robot demonstrations through SmolVLA adaptation, evaluation and error analysis to an inference API

Robot data 50 real SO-100 pick-and-place episodes, 19,631 frames at 30 fps
Split 40 episodes for training, 10 held out for evaluation
Model lerobot/smolvla_base, 450M parameters, fine-tuned for 2,000 steps
Result Held-out action MAE 5.876 → 4.419 between step 500 and step 2,000
Evaluation 377 held-out observations across 10 unseen episodes
Serving FastAPI action-chunk endpoint, ~255 ms p50 latency on an RTX 4090

Results

Every 10th frame of each held-out episode is passed to the policy as a robot would see it — two camera views, the joint state, and the task instruction — and the predicted 50-step action chunk is compared against the teleoperator's actions. Action units are raw SO-100 joint values.

Checkpoint Action MAE ↓ RMSE ↓ Chunk cosine ↑ p50 latency
step 500 5.876 8.913 0.9894 254 ms
step 1,000 4.965 7.992 0.9917 251 ms
step 1,500 4.659 7.574 0.9926 265 ms
step 2,000 4.419 7.387 0.9929 255 ms

Action prediction improves consistently across evaluated checkpoints on the held-out episodes, a 24.8% MAE reduction from the first evaluated checkpoint to the last, with no sign of overfitting within this budget.

Held-out action error and chunk similarity across checkpoints

Raw pretrained MAE is omitted because the base checkpoint and the target SO-100 dataset use non-comparable action normalization; see Evaluation scope.

Failure analysis

Aggregate improvement does not mean that all parts of the manipulation trajectory are equally difficult. Breaking the same predictions down by joint, by trajectory phase, along the prediction horizon and by episode separates what the model learned from what it did not.

Error broken down by action dimension, trajectory phase, chunk step and episode

Error is highest during early reaching and lowest late in the trajectory. Mean MAE is 5.45 in the early third of an episode, 4.24 in the middle and 2.88 in the late third — a 1.89× spread. This runs against the intuition that the grasp is the hard part.

The gripper is the hardest dimension. Normalized by each joint's own spread, error is highest on main_gripper (0.299) and lowest on main_shoulder_pan (0.158). The gripper command is close to binary, with a sharp open/close transition.

Error grows across the prediction horizon, from 1.83 at the first chunk step to 7.48 at the 50th. Expected for open-loop chunking: every step is predicted from one observation up to 1.67 s earlier, with no feedback.

Episodes vary more than checkpoints do. The worst held-out episode (39, MAE 5.21) is 1.5× the best (20, MAE 3.47) — a wider gap than between the first and last checkpoint, and the dominant source of uncertainty across 10 validation episodes.

The trajectory plots below show predicted (dashed) against demonstrated (solid) chunks at the highest-error moment of the three worst episodes. Look at the amplitude: where the demonstration sweeps through a large reach, the prediction stays comparatively flat, and the gripper channel holds near zero while the demonstration steps to its closed value.

Predicted versus demonstrated action chunks for the worst held-out episodes

Camera views at the highest-error moment of each worst episode

Together with the phase breakdown, this pattern suggests that perception-conditioned reaching — resolving where the cube is before the approach — may be a more useful target for improvement than the grasp itself. That is a hypothesis this offline setup cannot settle; testing it calls for multi-seed runs and closed-loop evaluation.

Per-observation errors are in artifacts/error_analysis.csv, one row per evaluated frame broken down by action dimension and trajectory phase.

Sensitivity to visual perturbation

The final checkpoint re-evaluated under three synthetic corruptions:

Perturbation Action MAE vs clean
clean 4.251
brightness −0.2 4.322 +1.7%
brightness +0.2 4.240 −0.3%
Gaussian blur σ=1 5.298 +24.6%
Gaussian blur σ=3 5.766 +35.6%
20% center occlusion 5.726 +34.7%

Action error under brightness, blur and occlusion perturbations

Brightness shifts barely move the error, consistent with the frozen SigLIP encoder's normalization. Blur and occlusion cost 25–36%, and blur reaches most of its damage already at σ=1 — camera focus and lens obstruction would deserve more attention than lighting in a deployment. The clean baseline here is 4.251 rather than 4.419 because the sweep runs on a cheaper subsample (5 episodes at stride 20), so rows are comparable within this table only.

Data and model

lerobot/svla_so100_pickplace — real SO-100 arm teleoperation in LeRobotDataset v3.0 format.

Episodes / frames 50 / 19,631
Split 40 train / 10 held out (seed 42) → 15,911 / 3,720 frames
Episode length 326–575 frames (10.9–19.2 s, mean 13.1 s)
Cameras top and wrist, 480×640 RGB, AV1
State / action 6-D each: shoulder pan/lift, elbow flex, wrist flex/roll, gripper
Task "Pick up the cube and place it in the box."

Trajectory length, action and state distributions across the dataset

One sample as the policy consumes it — language, two camera views and proprioceptive state in, a 50-step ground-truth action chunk out:

A single observation: instruction, camera views, robot state and the ground-truth action chunk

lerobot/smolvla_base is a 450M-parameter SmolVLM2 vision-language backbone with a flow-matching action expert. One forward pass emits 50 future actions, 1.67 s of control at 30 fps. Fine-tuning follows the SmolVLA defaults: the vision encoder stays frozen and the action expert is trained.

Training
Steps / batch size 2,000 / 8
Learning rate 1e-4, 100 warm-up steps, decayed over 2,000
Precision bfloat16 autocast
Hardware RTX 4090 (24 GB)
Wall clock 6 min 18 s
Training loss 0.764 → 0.121 (min 0.092)

Fine-tuning loss and learning-rate schedule

Engineering design

The pipeline is a sequence of commands, each writing artifacts the next one reads:

Stage Command Output
Inspect + split evla-inspect artifacts/dataset/, artifacts/splits/
Fine-tune evla-train outputs/<profile>/checkpoints/, artifacts/training/
Evaluate evla-evaluate artifacts/metrics/evaluation.{json,csv}
Error analysis evla-error-analysis artifacts/error_analysis.csv, figures
Robustness evla-robustness artifacts/robustness.csv
Report evla-report assets/results/, artifacts/provenance.json
Serve evla-infer, uvicorn embodied_vla.api:app action chunks over HTTP

Four decisions shape what the numbers mean:

  • Episode-level splitting. At 30 Hz adjacent frames are near-duplicates, so the split is over whole episodes and written to manifests that every later stage reads.
  • Padding is masked. LeRobot pads action chunks past the end of an episode by repeating the final frame; every metric ignores those steps rather than scoring against them. In this evaluation 6.6% of chunk steps are padding.
  • Schedule fitting. SmolVLA's shipped 1,000-step warm-up and 30,000-step decay are rescaled to the requested step budget. The effective training configuration and run provenance are captured with each experiment.
  • Configuration over code. Every dataset id, model id, revision, split rule and hyper-parameter lives in configs/*.yaml, so retargeting a different LeRobot-format dataset is a config change.

Training delegates to the official LeRobot trainer rather than reimplementing it, and platform-specific compatibility handling is isolated from upstream source. Details in docs/implementation.md and docs/platform_notes.md.

Inference and deployment

A trained checkpoint is served behind a typed HTTP interface: one observation in, one action chunk out.

Endpoint Purpose
GET /healthz Liveness; answers 200 with a reason even when the model failed to load
GET /model-info Served checkpoint, device, chunk size, expected cameras and state dim
POST /v1/predict One observation → one action chunk
// POST /v1/predict
{
  "instruction": "Pick up the cube and place it in the box.",
  "state": [0.1, -0.4, 0.5, 0.6, 0.8, 0.0],
  "images": { "top": "<base64 png>", "wrist": "<base64 png>" }
}
// 200 OK
{
  "action_chunk": [[15.68, -18.37, 23.65, 53.87, 69.53, 11.37], "… 50 steps total"],
  "latency_ms": <float>,   // server-side time measured for this request
  "model": "./outputs/portfolio/checkpoints/002000/pretrained_model",
  "device": "cuda"
}

latency_ms is measured per request and varies call to call. Measured inference performance: 254.5 ms p50 on an RTX 4090 for the held-out evaluation workload (372 timed calls at batch size 1, covering preprocess → forward → un-normalize).

The same loading path backs a dataset-replay mode, so inference can be exercised without robot hardware:

uv run python -m embodied_vla.inference --episode 39 --frame 100
Instruction : "Pick up the cube and place it in the box."
Episode 39, frame 100 | pretrained_model on cuda
Chunk shape : (50, 6) (chunk_size x action_dim)
MAE vs demo : 3.4757

The Docker image carries code and a frozen dependency set; datasets, checkpoints, the Hugging Face cache and credentials are mounted at run time.

Quick start

Requires Python 3.12.x, uv, and a CUDA GPU for training.

uv sync --extra dev
scripts/inspect_data.sh        # dataset summary, statistics, split manifests
scripts/train_smoke.sh         # ~1 min, validates the pipeline end to end
scripts/train_portfolio.sh     # ~6 min on an RTX 4090, the reported run
scripts/evaluate.sh            # evaluation, error analysis, robustness, report
scripts/serve.sh               # inference API on :8000
uv run pytest                  # 176 tests, no GPU and no dataset download
docker build -t evla:latest . && docker run --rm -p 8000:8000 -v "$PWD/outputs:/app/outputs:ro" evla:latest

Reproducibility

Model lerobot/smolvla_base @ c83c3163b8ca9b7e67c509fffd9121e66cb96205
Dataset lerobot/svla_so100_pickplace @ 728583b5eaf9e739a7f119e2def466fa1d552402
Split 40 train / 10 held out, seed 42, written to artifacts/splits/
Seeds split 42, training 1000, evaluation 42
Dependencies uv.lock, Python 3.12.7, PyTorch 2.11.0+cu128, LeRobot 0.6.1
Provenance artifacts/provenance.json

Model and dataset snapshots, package versions, episode splits and runtime configuration are pinned, and the revisions are consumed by the loaders rather than only declared in YAML. Full mechanics, Docker usage and the environment record are in docs/reproducibility.md.

Evaluation scope

Metrics here are offline, open-loop action-prediction metrics: how closely the policy reproduces recorded demonstrations on episodes it never saw during training. They are not closed-loop robot task-success rates — the policy never acts on a robot in this study, so compounding error, recovery and contact dynamics are untested.

Raw pretrained SmolVLA MAE is not reported. The base checkpoint un-normalizes actions with statistics from its pretraining mixture rather than SO-100 joint ranges, so its error would measure a normalization mismatch rather than policy quality. The checkpoint progression serves the comparison instead, since all checkpoints share the dataset's normalization.

Scope is one public dataset, one task, one seed and 10 held-out episodes, with 2,000 training steps — roughly one pass over the training frames, enough to adapt the action expert and well short of convergence. The perturbation sweep uses synthetic corruptions rather than real lighting or occlusion shifts.

Natural next steps: closed-loop evaluation in simulation to measure task success rather than action similarity; multi-seed runs with confidence intervals so checkpoint differences become testable; and targeted diagnostics for the early-reaching phase identified above.

Repository

configs/          smoke / portfolio / full experiment profiles
src/embodied_vla/ data, training, evaluation, error analysis, inference, API
scripts/          one entry point per pipeline stage
tests/            176 tests, no GPU or dataset required
artifacts/        generated metrics, splits, run records, provenance
assets/           architecture figure and generated result figures
docs/             reproducibility, implementation and platform notes

Built on LeRobot and SmolVLA; no LeRobot source is vendored. Robot data from lerobot/svla_so100_pickplace, distributed under Apache-2.0 according to its dataset card and downloaded from the Hub at run time rather than redistributed here. Code in this repository is MIT licensed (see LICENSE); the dataset's license governs that data and does not change it.

About

End-to-end SmolVLA adaptation and evaluation on real SO-100 demonstrations including training, failure analysis, robustness, and inference.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages