Ralf Römer1, Maximilian Seeliger2, Saida Liu1, Ben Sturgis1, Marco Bagatella2,3, Daniel Marta2, Andreas Krause2, Angela P. Schoellig1
1Technical University of Munich 2ETH Zurich 3MPI for Intelligent Systems
The official code repository for "Uncertainty Quantification for Flow-Based Vision-Language-Action Models".
Abstract: Vision-language-action models (VLAs) combine vision-language backbones with expressive generative action heads trained via flow matching on large-scale robotic datasets. Despite their strong empirical performance in robotic manipulation, VLAs lack mechanisms to quantify confidence in their predictions and to detect when their actions may be unreliable. This presents a critical limitation for real-world deployment in non-stationary environments, where models inevitably encounter scenarios outside their pretraining distribution and may fail without warning. To address this, we derive an efficient method for quantifying epistemic uncertainty in flow-matching models by leveraging velocity-field disagreement (VFD) across a small ensemble. We successfully use this uncertainty estimate for failure detection during deployment and active fine-tuning of flow-based VLAs. To this end, we propose SAVE, a framework for uncertainty-guided active multitask fine-tuning that reduces the number of costly expert demonstrations required to adapt VLAs to new tasks. Through extensive experiments on the LIBERO benchmark, we demonstrate that VFD yields better-calibrated uncertainty estimates predictive of downstream performance, that VFD achieves strong performance in detecting failures, and that uncertainty-guided data acquisition with SAVE requires at least 22% fewer samples than baselines. In summary, our work shows that quantifying epistemic uncertainty in flow-based VLAs improves both failure awareness and adaptation.
This Readme describes how to reproduce the results reported in the paper.
conda create -y -n uq_vla python=3.10
conda activate uq_vla
conda install ffmpeg=7.1.1 -c conda-forge
pip install -e ".[smolvla,libero,uncertainty]"uv venv --python 3.10
uv pip install "cmake<4"
uv pip install -e ".[smolvla,libero,uncertainty]"
uv pip install seaborn
source .venv/bin/activateCreate a .env file at the repo root with at least:
STORAGE_ROOT=/path/to/storage
HF_HOME=/path/to/huggingface_cacheBefore running any script in the rest of this README:
source .venv/bin/activate
export PYTHONPATH=src
source .envAll calibration figures use one iterative fine-tuning run with random episode selection, across 3 seeds and the first 15 rounds (--max_round 15):
uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s01
uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s23
uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s45
The calibration pipeline is split into a GPU compute layer and a cache-only plot layer.
bash/calibration/*— Compute (GPU, cluster). Run inference and write per-method, per-round caches into the run tree (round_*/calibration_comparison/<method>_episode_uncertainties.json).bash/paper_plots/*— Plot (cache-only, CPU). Re-read the caches and regenerate the paper figures. No GPU, runs in seconds.
Iterative fine-tuning with uniform/random selection, swept over seed pairs:
bash bash/calibration/calibration_all.sh \
configs/iterative_fine_tuning/calibration_experiment/uniform_leak3_weighted_pools_lr_schedule.yamlThis calls bash/active_learning/cluster_all.sh per seed pair, submitting a train +
eval SLURM job per round. Output lands in
outputs/iterative_fine_tuning/<run>_sXX/round_*/ with checkpoints and
round_evaluation.json (per-task success rates) per round.
Prerequisite: a pretrained SmolVLA ensemble (see bash/pretrain/).
bash bash/calibration/submit_calibration_comparison.sh \
plots/calibration_comparison/libero/uniform_leak3_weighted_pools_lr_schedule_history05_steps2000 \
outputs/iterative_fine_tuning/uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s01 \
outputs/iterative_fine_tuning/uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s23 \
outputs/iterative_fine_tuning/uniform_leak3_weighted_pools_lr_schedule_history05_steps2000_s45 \
-- --rounds 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14Submits one calibration_comparison.py job per seed (cluster_methods_comparison.sbatch)
plus an aggregation job. Caches are written per round under
round_*/calibration_comparison/. The aggregation job also creates per-round scatter
plots and the Table 1 summary chart.
Extra caches needed for some figures:
- Ensemble size ablation: requires ensemble members
00..03in each round (train extras withbash/active_learning/cluster_train_extra_members.sbatch), then compute the per-size caches:sbatch cluster_ensemble_size_ablation.sbatch \ --run_dirs <s01> <s23> <s45> \ --rounds 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 \ --ensemble_sizes 2 3 4 --env libero
- Language prompt variation: GPU rollouts under prompt perturbations:
bash bash/calibration/submit_language_variation.sh \ plots/language_variation/leak3fixed_weighted_pools_lr_schedule_history05_steps2000 \ <s01> <s23> <s45> -- --round 14
- Ensemble vs Laplace: fit the last-layer Laplace posterior per
round (
bash/calibration/fit_laplace.sh), then runcalibration_comparison.pyincluding thevfd_laplacemethod.
Once the caches exist, every figure regenerates in seconds:
bash bash/paper_plots/calibration_table.sh
bash bash/paper_plots/ensemble_ablation.sh
bash bash/paper_plots/language_variation.sh
bash bash/paper_plots/ensemble_vs_laplace.sh An M = 2 SmolVLA ensemble is iteratively fine-tuned on LIBERO-10 over R = 15 rounds. Each round prioritizes tasks by their mean VFD uncertainty (via a task-sampling temperature τ), requests n_e = 5 expert demonstrations, and fine-tunes with a 50/50 replay of pretraining data (λ = 0.5). All runs use 3 seeds.
| Setting | Value |
|---|---|
| Benchmark | LIBERO-10 (K = 10 tasks) |
| Ensemble | SmolVLA, M = 2 members (pretrained on Goal/Spatial/Object + 3 LIBERO-10 "leak" tasks) |
| Rounds R | 15 |
| Demonstrations per round n_e | 5 (75 total) |
| Gradient steps per round | 4000, batch size 32, cosine LR 5e-5 → 5e-6 (200 warmup) |
| Replay ratio λ | 0.5 |
| Task-sampling temperature τ | sweep {0, 1, 1.5, 2, 2.5, 3} (τ = 0 is uniform) |
| Seeds | 3 (1000, 1001, 1002) |
| Per-round evaluation | 30 rollouts, first ensemble member, ≤ 520 steps |
These match the imported configs under configs/iterative_fine_tuning/iter_al*/
(total_rounds: 15, episodes_per_round: 5, steps: 4000, replay 0.5/0.5,
two ensemble_model_paths, three multi_seed runs).
Each directory holds the temperature sweep t0, t1, t1.5, t2, t2.5, t3.
The launcher submits, per config, a train → eval → aggregate chain for every
seed declared in that config's multi_seed block:
# best VFD temperature
bash bash/active_learning/submit_multi_seed.sh \
configs/iterative_fine_tuning/iter_al/t2.5_w50_eps5_step4000_lr.yaml
# full VFD temperature sweep
bash bash/active_learning/submit_multi_seed.sh configs/iterative_fine_tuning/iter_al/*.yamlPer seed it submits a training job
(cluster_train_multi_seed_child.sbatch → iterative_fine_tuning.run_multi_seed_child)
and a dependent evaluation job
(cluster_eval_multi_seed_child.sbatch → iterative_fine_tuning.evaluate_run);
once all seeds finish, one aggregation job
(cluster_eval_multi_seed_aggregate.sbatch → iterative_fine_tuning.summarize_multi_seed_run)
summarizes success rates across seeds into multi_seed_evaluation.json.
The iter_al_diversity configs use a predefined_ranking selection strategy:
each round simply pops the next n_e not-yet-selected episodes from a
precomputed ranking file. The paper's diversity baseline ranks all
candidate initial observations by SigLIP k-center-greedy; that ranking is
computed offline, written to JSON, and referenced from the config via
selection.predefined_ranking_path.
A JSON list of objects, ordered most-diverse-first. Each entry must have at
least episode_id; optional fields (task_group, task_id, instruction,
frame_index) are recorded into the per-round selection manifest for
downstream analysis.
[
{"episode_id": 23, "task_group": "libero_10", "task_id": 2, "instruction": "..."},
{"episode_id": 11, "task_group": "libero_10", "task_id": 7, "instruction": "..."}
]The default path referenced by the diversity configs is
${STORAGE_ROOT}/outputs/active_learning/diversity_ranking_libero10.json.
Use scripts/diversity/k_greedy.py
(SigLIP so400m-patch14-384 + k-center-greedy over the candidate task pool); see
that script's --help for arguments. Its output should be written to the path
the config expects, in the format above.
predefined_ranking is implemented in
src/iterative_fine_tuning/selection.py
via _run_predefined_ranking_selection. It short-circuits at the top of
run_selection_round, skipping the policy / ensemble / uncertainty path, and
writes the usual SelectionManifest so the rest of the iterative loop (training
and evaluation) is unchanged. The companion config changes (allowing empty
ensemble_model_paths, adding the predefined_ranking_path field) live in
src/iterative_fine_tuning/config.py.
The multi-seed orchestration is a thin layer over main's existing single-run loop:
iterative_fine_tuning.main.run() dispatches to _run_multi_seed when a config
declares multi_seed seeds/runs, which builds a per-seed child config and calls
the unchanged single-run loop (_run_single). Evaluation reuses evaluate_run.py.
main's selection / training / evaluation behavior is unchanged, so the calibration
and single-seed results above are unaffected.
Reproduce the runtime failure-detection experiment. At each policy-inference timestep we compute the VFD epistemic
uncertainty score and flag the rollout as Fail once the score exceeds a
per-task conformal threshold calibrated on successful rollouts. The paper reports
Accuracy, True-Positive-Rate (TPR), True-Negative-Rate (TNR), normalized
Detection Time, and Timestep-Wise Accuracy (TWA), and — following the failure
prediction framework — averages over the conformal quantiles 0.90, 0.91, …, 0.99
rather than cherry-picking one confidence level.
| Code name | Paper name |
|---|---|
vfd |
VFD (ours) |
Baselines (Entropy, TC, RND-OE, ACE) are computed downstream — see Downstream FIPER metrics/plots below.
The reproduction has two layers. This repo covers the first (record + score); the FIPER metric computation and the final plot live in the separate FIPER repo.
- Record LIBERO-10 rollouts, caching the intermediate
ODE-trajectory tensors needed for scoring
(
src/lerobot/scripts/fiper_data_generation/record_fiper_rollout.py). - Score each recorded rollout with the M = 2 ensemble, producing the
per-timestep
vfd_onewayandvfd(VFD) uncertainty scores (src/lerobot/scripts/fiper_data_generation/score_fiper_rollout.py).
The vfd metric is implemented in
src/lerobot/fiper_data_generator/fiper_rollout_scorer.py
(compute_ensemble_vfd_scores, gated by the
bayesian_ensemble: [..., vfd] entry in the score config).
A trained M = 2 SmolVLA ensemble. The configs/scripts default to the round-15
checkpoints of the SAVE runs from
Active Fine-Tuning (SAVE), per seed s01 / s23 / s45:
outputs/iterative_fine_tuning/uniform_leak3fixed_weighted_pools_lr_schedule_history05_steps2000_<seed>/round_015/training/member_0{0,1}/checkpoints/last/pretrained_model
Override via the POLICY_CHECKPOINT, ENSEMBLE_MEMBER_00, ENSEMBLE_MEMBER_01
environment variables (see the scripts below).
The paper pipeline uses a single record/score config pair:
Config pair (record_fiper_rollout/, score_fiper_rollout/) |
Purpose |
|---|---|
libero10_all_tasks_5eps.yaml |
LIBERO-10, scores vfd_oneway + vfd |
Episode counts, ODE evaluation times, and the ensemble paths are set inside the
YAML — see configs/smolvla/score_fiper_rollout/libero10_all_tasks_5eps.yaml.
paper_plots/all_commands.sh is the end-to-end orchestrator. Its Step 1 is
this repo's portion; per seed it runs:
# record + one-way (vfd_oneway) scoring, sharded over the 10 tasks
scripts/fiper/run_libero10_seed_5eps_data.sh s01
# re-score the recorded rollouts with vfd (VFD)
scripts/fiper/run_libero10_seed_2way_scoring.sh s01The script layering is
run_libero10_seed_* → run_libero10_parallel_* (per-GPU task fan-out) →
run_libero10_task_* (one task → record_fiper_rollout.py / score_fiper_rollout.py).
Useful env vars: TASK_IDS, GPU_IDS, MAX_PARALLEL, SEED_BASE,
EXPERIMENT_NAME. Scored output lands in
outputs/fiper_rollout_scoring/<experiment>/libero_10/. For a quick single-task
check, run one task shard directly:
scripts/fiper/run_libero10_task_5eps_data.sh 0.
Steps 2–5 of paper_plots/all_commands.sh — computing the FIPER detection metrics
for all methods, merging the vfd results, and rendering
Figure 12 via scripts/plot_seed_mean_accuracy_detection.py — run against the
separate FIPER repository. The
all_commands.sh reference (with FIPER_ROOT-relative paths) is included as a
record of the full cross-repo workflow; integrating those steps into this repo is
a follow-up.
If you find this work useful, please consider citing our paper:
@article{romer2026uq_vla,
title={Uncertainty Quantification for Flow-Based Vision-Language-Action Models},
author={Ralf R{\"o}mer and Maximilian Seeliger and Saida Liu and Ben Sturgis and Marco Bagatella and Daniel Marta and Andreas Krause and Angela P. Schoellig},
journal={arXiv preprint arXiv:2606.18043},
year={2026}
}This work builds upon:
We thank the authors of these projects for their open-source contributions.