Yunfan Ren · Zhiyuan Zhu · Jiaxu Xing · Davide Scaramuzza
Robotics and Perception Group, University of Zürich
Robotics: Science and Systems (RSS) 2026
A self-adaptive framework that learns agile quadrotor flight directly in the real world — without precise system identification, without offline Sim2Real transfer, and without conservative safety margins. The system evolves a base policy from a peak speed of 2.0 m/s to 7.3 m/s within roughly 100 seconds of physical flight time.
🌐 Project Page: rpg.ifi.uzh.ch/lafr
If you find our work and/or code useful, please cite our paper:
@inproceedings{ren2026agile,
title = {Learning Agile Quadrotor Flight in the Real World},
author = {Ren, Yunfan and Zhu, Zhiyuan and Xing, Jiaxu and Scaramuzza, Davide},
booktitle = {Robotics: Science and Systems (RSS)},
year = {2026}
}We present LAFR, a self-adaptive learning framework that achieves agile quadrotor flight directly in the real world, eliminating the need for precise system identification, offline Sim2Real transfer, and conservative safety margins. The framework chains three load-bearing contributions: (1) a hybrid quadrotor ODE in which rigid-body dynamics and a learned residual share a single continuous-time integrator, integrated by a Lie-group RK4 scheme on SO(3) so ω-residuals propagate into translational state within a single step; (2) a consistent-IO residual MLP trained AD-through-integrator with spectral-norm regularization, whose loss runs the integrator inside the optimization and minimizes the geodesic distance to the measured next state; and (3) Adaptive Temporal Scaling (ATS) via closed-loop sensitivity in β = 1/α, with a smooth quadratic barrier and an SO(3) geodesic component in the tracking-error metric. Together these implement RASH-BPTT (Real-world Anchored Short-horizon Backpropagation Through Time) for in-flight policy updates.
The code has been tested on:
Ubuntu: 22.04 / 24.04 LTS
Python: 3.11 (conda env recommended)
CUDA: 12 / 13 (GPU), or CPU
GPU: RTX 4090 / 5090 (Blackwell)Clone the repo and set up the conda environment. We recommend Mambaforge because it resolves the JAX/CUDA stack in seconds where stock conda can take minutes:
git clone https://github.com/uzh-rpg/LAFR.git
cd LAFR
mamba create -n lafr python=3.11 -y
mamba activate lafrGPU host (CUDA 13 / Blackwell):
pip install --upgrade "jax[cuda13]"GPU host (CUDA 12):
pip install --upgrade "jax[cuda12]"CPU-only:
pip install --upgrade "jax[cpu]"Then install LAFR:
pip install -e ".[dev]"
export XLA_PYTHON_CLIENT_PREALLOCATE=falseLAFR has a single entry point that chains base-policy training and online learning end-to-end:
$ python -m lafr.online_learning.run_pipeline --cfg lafr/cfg/online.yamlEach invocation:
- Trains a base tracking policy from
lafr/cfg/base.yaml(~22 s on an RTX 5090, 2048 envs × 500 epochs by default) and saves it underoutputs/tracking/v*/policy_params. - Runs the online residual-learning + ATS loop, writing versioned policies, datasets, ATS diagnostics, metrics, and a Rerun recording to
outputs/online_learning/v*/.
Useful flags:
| Flag | Effect |
|---|---|
--base-policy-dir <path> |
Reuse an existing checkpoint, skipping base-policy training |
--skip-base-training |
Require an existing base policy; do not retrain |
--base-cfg <path> / --base-log-dir <path> |
Override the base-training config or output dir |
--iterations N / --policy-epochs N |
Override defaults from online.yaml |
--smoke |
Shrink both stages to a few epochs for a quick CPU sanity check (used by CI) |
--no-record-rerun |
Disable the bundled Rerun .rrd recording |
The pipeline writes a Rerun recording to the run dir. Activate the conda env (so the bundled rerun viewer is on PATH) and open the recording on the host:
conda activate lafr
rerun outputs/online_learning/<run_dir>/online_learning.rrdThe bundled blueprint sets sim_time as the default timeline and pre-lays out a 3D spatial view plus five time-series panels:
| Path | Description |
|---|---|
world/drone_model |
Per-step body-frame RGB triad (red/green/blue = body x/y/z) |
world/drone_trail |
Short rolling tail of the drone position |
world/reference |
Figure-8 reference path |
world/reference_target |
Current reference target |
world/wind |
Subtle drifting arrow grid showing the applied wind acceleration |
world/policy_version |
Rasterized "policy vN alpha=…" label, world-space geometry |
timeseries/speed |
Speed [m/s] over the rollout |
timeseries/omega |
3-axis body rate cmd + feedback, Y range pinned to ±7 rad/s |
timeseries/thrust |
Commanded total thrust |
timeseries/tracking_error |
Position error norm |
timeseries/time_scale |
Reference time scale α |
The core library is the lafr/ package. For more on each sub-module see its own README.md (where present).
| Component | Summary |
|---|---|
algos/ |
BPTT trainer + ATS closed-loop sensitivity step (paper Eq. 8–11) |
cfg/ |
base.yaml (base-policy training) + online.yaml (online pipeline) |
envs/ |
JAX state-based trajectory-tracking environment + wrappers |
modules/ |
Policy MLP and residual MLP (paper Eq. 5) |
objects/ |
quadrotor.py (high-level), quadrotor_dynamics.py (Lie-group RK4 primitive), reference trajectories |
online_learning/ |
The pipeline as a sub-package:run_pipeline.py (entry point) + cli.py / tui.py / rerun_logging.py / io.py / env_setup.py / pipeline.py |
scripts/ |
train_base_policy implementation, called by run_pipeline |
tests/ |
Env-level pytest sanity + bit-exact consistency test |
The repository also ships tools/diff_run.py, an Orbax-aware bit-exact diff used by the pytest consistency check.
# Quick CPU end-to-end smoke run (the CI bar):
$ JAX_PLATFORMS=cpu python -m lafr.online_learning.run_pipeline --smoke \
--seed 42 --no-record-rerun \
--output-dir outputs/_baseline_online \
--base-log-dir outputs/_baseline_tracking
# Verify the pipeline output is bit-exact against a saved baseline:
$ LAFR_BASELINE_DIR=outputs JAX_PLATFORMS=cpu pytest -q lafr/teststools/diff_run.py compares two run directories Orbax-aware (it loads each checkpoint and compares the deserialized tensors, since Orbax's on-disk layout drifts across saves while the underlying floats are deterministic).
This work was developed at the Robotics and Perception Group, University of Zürich. The Python-only differentiable simulator is built on JAX and Flax; the visualizer is Rerun. We thank the authors of flightning for open-sourcing their differentiable quadrotor simulator, which provided the foundation of this codebase, and the authors of learning_on_the_fly for this README template. We also acknowledge the use of Claude (Anthropic) and Codex (OpenAI) as coding assistants during the refactoring and cleanup of this codebase.