Any-to-any forward-dynamics cotraining for robot learning from video.
FloMo fine-tunes the Wan 2.2 TI2V-5B video model (LoRA by default) to predict any
subset of {robot action, future video, motion tokens} from {image, language}.
Because action is only one of the outputs, one model can learn from robot data with
actions and from human video without actions.
The paper trains on EgoVerse (egocentric human video), MolmoAct2, and RealYAM (real robot), then fine-tunes on RoboTwin 2.0 (simulation).
Contents: Install · Pretrained checkpoints · Inference · Fine-tune on RoboTwin 2.0 · Train on your own data · Preprocessing reference · Motion tokens · Code layout
You need a CUDA GPU and Python 3.10. Run every command from the repo root.
conda create -n flomo python=3.10 -y && conda activate flomo
git clone https://github.com/pairlab/flomo.git && cd flomo
# 1. Backbone code. FloMo imports these clones from the repo root.
git clone https://github.com/Wan-Video/Wan2.2.git
git clone https://github.com/dreamzero0/dreamzero.git
# 2. PyTorch (cu128 shown; Blackwell GPUs need CUDA 12.8+), then FloMo.
pip install "torch==2.10.*" "torchvision==0.25.*" --index-url https://download.pytorch.org/whl/cu128
pip install -e .
# 3. Wan VAE and text-encoder weights (into ./wan_checkpoints).
hf download Wan-AI/Wan2.2-TI2V-5B Wan2.2_VAE.pth models_t5_umt5-xxl-enc-bf16.pth \
--local-dir ./wan_checkpointsThe Wan DiT and the umT5 tokenizer download automatically on first use.
For preprocessing only (to make motion tokens from your own videos), also install the 3D trackers. Both clones go in the repo root:
git clone https://github.com/henry123-boy/SpaTrackerV2.git
git clone https://github.com/facebookresearch/vggt.git
pip install "numpy<2" kornia==0.8.1 moviepy==1.0.0 pycolmap==3.11.1 pyceres==2.4 \
scikit-learn flow_vis jaxtyping \
"utils3d @ git+https://github.com/EasternJournalist/utils3d.git@d3a577acf0a9ad7e513a1416449a07b6f47d967f"Version notes:
- Keep
torchvision<=0.25, because SpaTrackerV2 usestorchvision.io.write_video, which 0.26 removed. - Keep
numpy<2on the same line, because without it pip installs numpy 2. - Do not install SpaTrackerV2's
requirements.txt, because it pins anxformersthat needs an older torch.
The three paper base models are on Hugging Face at shitijgovil/FloMo. All three use the same training data. They differ only in what they predict:
| File | Predicts |
|---|---|
ma/flomo_ma.pt |
motion tokens + action |
va/flomo_va.pt |
video + action |
vma/flomo_vma.pt |
video + motion tokens + action |
hf download shitijgovil/FloMo vma/flomo_vma.pt --local-dir ./flomo_ckptsinfer_example.py takes camera images, a task string, and the current end-effector
pose. It prints a chunk of future actions with shape (T, 14): for each of the two
arms, x, y, z, yaw, pitch, roll, gripper.
python infer_example.py --checkpoint ./flomo_ckpts/vma/flomo_vma.pt \
--wan-ckpt-dir ./wan_checkpoints \
--image primary.png --image left.png --image right.png \
--task "pick up the croissant and place it in the oven" \
--ee-pose="x,y,z,yaw,pitch,roll,grip,x,y,z,yaw,pitch,roll,grip"- Give one
--imageper camera, in the orderprimary,left,right. - Write
--ee-pose=...with=, because a negative first value otherwise looks like a flag. --normalizer-typeselects the action scale:realyam(default) for the base checkpoints,robotwinfor RoboTwin fine-tunes.
The script only predicts actions. Camera capture and robot control depend on your setup.
This walk-through fine-tunes one RoboTwin task (open_laptop) from a base checkpoint.
It is the paper setup: 50 Aloha-AgileX demo_clean demos, 5,000 updates at batch
size 256. Preprocessing needs the tracker from Install.
1. Download the demos.
hf download TianxingChen/RoboTwin2.0 dataset/open_laptop/demo_clean.zip \
--repo-type dataset --local-dir <RoboTwin>/data/hf
unzip <RoboTwin>/data/hf/dataset/open_laptop/demo_clean.zip -d <RoboTwin>/data/demo_clean2. Preprocess: 3D tracks and text embeddings.
for mode in tracks text_emb; do
python -m preprocessing.preprocess_robotwin mode=$mode task_names=[open_laptop] \
source_dir=<RoboTwin>/data/demo_clean dest_dir=<RoboTwin>/data/flomo
done3. Fine-tune. resume=false loads only the weights, so the optimizer and step
count start fresh.
python train.py --config-name train_robotwin data_root=<RoboTwin>/data \
robotwin_task=open_laptop checkpoint=./flomo_ckpts/vma/flomo_vma.pt \
resume=false run_name=robotwin_open_laptopCheckpoints go to checkpoints/forward_dynamics/robotwin_open_laptop/<epoch>.pt.
VA and VMA print a warning about unused video_primary keys. This is expected,
because the RoboTwin config does not predict video.
4. Run it. Use Inference with --normalizer-type robotwin. To score
the policy, use RoboTwin's policy evaluator.
A dataset config in cfg/datasets/ says which data to use and what each dataset
gives as input and output. cfg/train.yaml holds the training settings, and its
comments explain every key.
datasets= |
Use |
|---|---|
template (default) |
Annotated example. Copy it and fill in the ??? fields. |
flomo |
The paper mixture (EgoVerse + MolmoAct2 + RealYAM). |
robotwin |
One RoboTwin task (use --config-name train_robotwin). |
# Your own data
python train.py datasets=my_dataset run_name=my_run
# Start from a base checkpoint (weights only)
python train.py datasets=my_dataset checkpoint=./flomo_ckpts/vma/flomo_vma.pt resume=false run_name=my_ft
# Paper mixture, 8 GPUs on one node
torchrun --nproc_per_node=8 train.py datasets=flomo data_root=/path/to/flomo_data run_name=flomo_repro- Resume. With
resume=true(the default), a run with the samerun_namecontinues from its last checkpoint. - Overrides. You can set any config key on the command line, for example
optim.lr=5e-5 batch_size=64 gpu_max_bs=16.batch_sizeis the global batch;gpu_max_bsis the per-GPU micro-batch. - Cache. The first run computes motion statistics for each dataset and caches
them in
~/.cache/flomo/track_stats. SetFLOMO_CACHE_DIRto change the location. - Shared arguments in
flomo.yaml. The anchordataset_defaultsholds the shared dataset arguments. YAML expands it before command-line overrides, sodatasets.0.args.sample_stride=2changes only the first dataset.
Each dataset has a script preprocessing/preprocess_<dataset>.py with a config
cfg/preprocessing/preprocess_<dataset>.yaml. Set mode= to run one stage:
| Dataset | Modes |
|---|---|
| RoboTwin | tracks, text_emb, vae_latent_h16 |
| RealYAM | tracks, text_emb, vae_latent_h16 |
| MolmoAct2 | tracks, video, text, text_emb, vae_latent, vae_latent_h16, actions |
| EgoVerse | tracks, video, text, text_emb, meta, vae_latent_h16 |
-
tracksmakes the 3D tracks for motion tokens.text_embcaches the umT5 text embeddings that training reads. -
EgoVerse order. Run
videobeforetracksandvae_latent_h16, andtextbeforetext_emb.datasets=flomoneedsvideo,tracks,vae_latent_h16,text, andtext_emb. In training, setdata_rootto the preprocessingdest_dir. The dataset names are<output_name>_<split>. -
MolmoAct2 subsets. Build an episode index, select a subset by hours, then preprocess the subset:
python -m preprocessing.preprocess_molmoact2 index --index-dir /idx build --source-dir /raw python -m preprocessing.preprocess_molmoact2 index --index-dir /idx select --name subset --hours 10 python -m preprocessing.preprocess_molmoact2 index_path=/idx/subset/index.jsonl mode=video
Motion tokens are dense 3D scene flow, encoded by the frozen Wan VAE as if it were a 3-channel video:
- Track (
preprocessing/SpatialTracker.py). SpatialTrackerV2 tracks a 64×64 grid of points (4,096 tracks) in 3D, in the camera frame of the window's first frame. It restarts every 16 frames. - Load (
BaseDataset.load_tracks). Withsupervision=cumulative_disp, each 16-frame window becomes the displacement from frame 0:(1, 16, 4096, 3). - Encode (
MotionTokensWanPretrainedModality). The modality makes an XYZ grid(3, 15, 64, 64), resizes it to 256, scales it per dataset to[-1, 1](flomo/utils/motion_stats.py), and encodes it with the Wan VAE into a(48, 5, 16, 16)latent. The Wan patch embedding turns the latent into tokens.
supervision=point_tracks and supervision=optical_flow encode per-frame track
changes instead. All datasets that output motion tokens must use the same mode.
train.py # training entry point (Hydra)
infer_example.py # single-step action inference example
cfg/
train.yaml, train_robotwin.yaml
datasets/ # template, flomo, robotwin
preprocessing/ # one config per preprocess_<dataset>.py
flomo/
models/FloMo.py # Wan trunk, token order, flow-matching timesteps, _build_modality
models/modalities.py # Modality contract + image/video/text/action/motion-token modalities
loaders/ # BaseDataset, per-dataset readers, cotrain_loader (sampling, batching)
utils/ # normalizers, motion stats, Wan encoding, training, visualization
preprocessing/ # trackers (SpatialTracker.py, VGGT.py), writers, per-dataset pipelines
- Add a dataset. Subclass
BaseDatasetand register it incotrain_loader._dataset_class. - Add a modality. Implement the
Modalitycontract inmodels/modalities.pyand add akindbranch to_build_modality.
MIT. See LICENSE.