Bidhan Roy commited on
Commit ·
a4cfa9f
0
Parent(s):
Release WorldDiT model and documentation
Browse files- .gitattributes +36 -0
- README.md +354 -0
- checkpoints/libero_10/model.safetensors +3 -0
- checkpoints/libero_goal/model.safetensors +3 -0
- checkpoints/libero_object/model.safetensors +3 -0
- checkpoints/libero_spatial/model.safetensors +3 -0
- config.json +11 -0
- dependencies/ViT-B-32.pt +3 -0
- dependencies/mae_pretrain_vit_base.pth +3 -0
- eval.py +340 -0
- inference.py +431 -0
- replay.mp4 +3 -0
- requirements.txt +32 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
replay.mp4 filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
pipeline_tag: robotics
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
tags:
|
| 7 |
+
- worlddit
|
| 8 |
+
- world-action-model
|
| 9 |
+
- world-models
|
| 10 |
+
- libero
|
| 11 |
+
- robot-learning
|
| 12 |
+
- robotic-manipulation
|
| 13 |
+
- imitation-learning
|
| 14 |
+
- diffusion-transformer
|
| 15 |
+
- flow-matching
|
| 16 |
+
- pareto-frontier
|
| 17 |
+
- parameter-efficiency
|
| 18 |
+
inference: false
|
| 19 |
+
license: cc-by-4.0
|
| 20 |
+
widget:
|
| 21 |
+
- example_title: "LIBERO Spatial, task 5"
|
| 22 |
+
text: "Successful rollout, front view."
|
| 23 |
+
output:
|
| 24 |
+
url: "https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_spatial_frontview_task05_episode01.mp4"
|
| 25 |
+
- example_title: "LIBERO Object, task 8"
|
| 26 |
+
text: "Successful rollout, agent view."
|
| 27 |
+
output:
|
| 28 |
+
url: "https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_object_agentview_task08_episode01.mp4"
|
| 29 |
+
- example_title: "LIBERO Goal, task 10"
|
| 30 |
+
text: "Successful rollout, side view."
|
| 31 |
+
output:
|
| 32 |
+
url: "https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_goal_sideview_task10_episode01.mp4"
|
| 33 |
+
- example_title: "LIBERO Long, task 6"
|
| 34 |
+
text: "Successful rollout, front view."
|
| 35 |
+
output:
|
| 36 |
+
url: "https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_10_frontview_task06_episode01.mp4"
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
<p align="center">
|
| 40 |
+
<img src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/paris2/model-card/v1/bagel_labs_logo.png" alt="Bagel Labs">
|
| 41 |
+
</p>
|
| 42 |
+
|
| 43 |
+
<h1 align="center">WorldDiT: A Unified Diffusion Backbone for<br>World and Action Modeling</h1>
|
| 44 |
+
|
| 45 |
+
<p align="center">
|
| 46 |
+
<a href="https://arxiv.org/abs/2607.23909">
|
| 47 |
+
<img src="https://img.shields.io/badge/arXiv-2607.23909-B31B1B.svg?logo=arxiv&logoColor=white" alt="WorldDiT paper on arXiv">
|
| 48 |
+
</a>
|
| 49 |
+
</p>
|
| 50 |
+
|
| 51 |
+
<p align="center">
|
| 52 |
+
WorldDiT couples continuous action generation with auxiliary future normalized
|
| 53 |
+
RGB patch prediction in one diffusion transformer. The architecture is designed
|
| 54 |
+
as a general backbone for world and action modeling, while the current release
|
| 55 |
+
evaluates it on LIBERO and provides four checkpoints, a self contained
|
| 56 |
+
inference runtime, and an evaluator.
|
| 57 |
+
</p>
|
| 58 |
+
|
| 59 |
+
## See WorldDiT act
|
| 60 |
+
|
| 61 |
+
The four clips below show successful rollouts from the released checkpoints.
|
| 62 |
+
Each clip covers a different LIBERO suite and camera view.
|
| 63 |
+
|
| 64 |
+
<table>
|
| 65 |
+
<tr>
|
| 66 |
+
<td width="50%" valign="top">
|
| 67 |
+
<video width="100%" controls muted loop playsinline preload="metadata"
|
| 68 |
+
src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_spatial_frontview_task05_episode01.mp4"></video>
|
| 69 |
+
<strong>LIBERO Spatial</strong><br>
|
| 70 |
+
Task 5, front view.
|
| 71 |
+
</td>
|
| 72 |
+
<td width="50%" valign="top">
|
| 73 |
+
<video width="100%" controls muted loop playsinline preload="metadata"
|
| 74 |
+
src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_object_agentview_task08_episode01.mp4"></video>
|
| 75 |
+
<strong>LIBERO Object</strong><br>
|
| 76 |
+
Task 8, agent view.
|
| 77 |
+
</td>
|
| 78 |
+
</tr>
|
| 79 |
+
<tr>
|
| 80 |
+
<td width="50%" valign="top">
|
| 81 |
+
<video width="100%" controls muted loop playsinline preload="metadata"
|
| 82 |
+
src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_goal_sideview_task10_episode01.mp4"></video>
|
| 83 |
+
<strong>LIBERO Goal</strong><br>
|
| 84 |
+
Task 10, side view.
|
| 85 |
+
</td>
|
| 86 |
+
<td width="50%" valign="top">
|
| 87 |
+
<video width="100%" controls muted loop playsinline preload="metadata"
|
| 88 |
+
src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_10_frontview_task06_episode01.mp4"></video>
|
| 89 |
+
<strong>LIBERO Long</strong><br>
|
| 90 |
+
Task 6, front view.
|
| 91 |
+
</td>
|
| 92 |
+
</tr>
|
| 93 |
+
</table>
|
| 94 |
+
|
| 95 |
+
[Spatial MP4](https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_spatial_frontview_task05_episode01.mp4)
|
| 96 |
+
· [Object MP4](https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_object_agentview_task08_episode01.mp4)
|
| 97 |
+
· [Goal MP4](https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_goal_sideview_task10_episode01.mp4)
|
| 98 |
+
· [Long MP4](https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v1/worlddit_libero_10_frontview_task06_episode01.mp4)
|
| 99 |
+
|
| 100 |
+
## Release snapshot
|
| 101 |
+
|
| 102 |
+
| Reported LIBERO result | Released model |
|
| 103 |
+
|---|---|
|
| 104 |
+
| **94.9 percent** mean success<br>**1,898 of 2,000** successful episodes | **399.084 million** total parameters<br>**135.107 million** trainable parameters |
|
| 105 |
+
| **98.0 percent** Spatial<br>**97.0 percent** Object | **Three** observation steps<br>**Seven** predicted actions |
|
| 106 |
+
| **92.8 percent** Goal<br>**91.8 percent** Long | **Three** actions executed before replanning<br>**Seven** action dimensions |
|
| 107 |
+
|
| 108 |
+
| Checkpoints | Runtime | Encoders and environment |
|
| 109 |
+
|---|---|---|
|
| 110 |
+
| Spatial<br>Object<br>Goal<br>Long | `inference.py`<br>`eval.py`<br>`config.json` | MAE ViT B<br>OpenAI CLIP ViT B 32<br>SafeTensors and pinned requirements |
|
| 111 |
+
|
| 112 |
+
The repository is self contained for WorldDiT inference. LIBERO provides the
|
| 113 |
+
benchmark environments, assets, task definitions, and initial states.
|
| 114 |
+
|
| 115 |
+
The released runtime and checkpoints were revalidated from a clean installation
|
| 116 |
+
on eight RTX Pro 6000 Blackwell GPUs. The reported aggregate covers five
|
| 117 |
+
hundred simulator episodes per suite. Three hundred episodes per suite informed
|
| 118 |
+
staged checkpoint selection, while two hundred episodes per suite were disjoint
|
| 119 |
+
from selection.
|
| 120 |
+
|
| 121 |
+
### Parameter count and reported success
|
| 122 |
+
|
| 123 |
+
Among methods with complete four suite averages, WorldDiT lies on the reported
|
| 124 |
+
Pareto frontier for total model parameters and mean LIBERO success.
|
| 125 |
+
|
| 126 |
+
<p align="center">
|
| 127 |
+
<img src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v2/worlddit_parameter_efficiency.png" alt="Reported mean LIBERO success against total instantiated model parameters">
|
| 128 |
+
</p>
|
| 129 |
+
|
| 130 |
+
<p align="center">
|
| 131 |
+
<sub>Reported LIBERO success against total model parameters for 24 methods. The line connects methods on the Pareto frontier with complete four suite averages. Because the methods follow different published evaluation protocols, the figure summarizes published results rather than a direct comparison under one common evaluation protocol.</sub>
|
| 132 |
+
</p>
|
| 133 |
+
|
| 134 |
+
## Run a smoke test
|
| 135 |
+
|
| 136 |
+
Download the repository and create a clean Python 3.12 environment.
|
| 137 |
+
|
| 138 |
+
```bash
|
| 139 |
+
hf download bageldotcom/worlddit --local-dir worlddit
|
| 140 |
+
cd worlddit
|
| 141 |
+
|
| 142 |
+
python3.12 -m venv venv
|
| 143 |
+
source venv/bin/activate
|
| 144 |
+
python -m pip install -r requirements.txt
|
| 145 |
+
python -m pip install --no-deps robosuite==1.4.1
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
LIBERO supplies the benchmark definitions, assets, and initial states. Keep the
|
| 149 |
+
checkout at `~/LIBERO`, which is the evaluator's default.
|
| 150 |
+
|
| 151 |
+
```bash
|
| 152 |
+
git clone https://github.com/Lifelong-Robot-Learning/LIBERO.git ~/LIBERO
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
The released evaluation was validated with LIBERO commit
|
| 156 |
+
`8f1084e3132a39270c3a13ebe37270a43ece2a01`.
|
| 157 |
+
|
| 158 |
+
```bash
|
| 159 |
+
python eval.py \
|
| 160 |
+
--suite libero_spatial \
|
| 161 |
+
--gpus 1 \
|
| 162 |
+
--tasks 1 \
|
| 163 |
+
--episodes 1 \
|
| 164 |
+
--max-steps 20 \
|
| 165 |
+
--output-dir results/smoke
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
A successful smoke test confirms that the environment, checkpoint, visual
|
| 169 |
+
encoders, simulator, and rendering path load together. Full benchmark reporting
|
| 170 |
+
uses complete suite evaluations.
|
| 171 |
+
|
| 172 |
+
## How WorldDiT works
|
| 173 |
+
|
| 174 |
+
Each of three recent observation steps contributes primary and wrist images
|
| 175 |
+
together with robot state, while one language instruction conditions the
|
| 176 |
+
sequence. During training, one diffusion transformer backbone learns a seven
|
| 177 |
+
step action chunk together with an auxiliary future normalized RGB patch
|
| 178 |
+
target. At deployment, the encoded history conditions the action velocity field
|
| 179 |
+
directly. RGB patch token construction and RGB prediction head evaluation
|
| 180 |
+
remain outside the inference graph, concentrating computation on action
|
| 181 |
+
generation. The controller executes the first three predicted actions, observes
|
| 182 |
+
again, and replans.
|
| 183 |
+
|
| 184 |
+
<p align="center">
|
| 185 |
+
<img src="https://pub-2c09ae97630f4932a23e622b450076e0.r2.dev/worlddit/model-card/v2/worlddit_inference_pipeline.png?v=09d6508" alt="WorldDiT inference pipeline">
|
| 186 |
+
</p>
|
| 187 |
+
|
| 188 |
+
<p align="center">
|
| 189 |
+
<sub>WorldDiT inference pipeline. The encoded observation history conditions action generation through twenty flow steps. The controller executes the first three actions from each seven action chunk, then updates the window and replans.</sub>
|
| 190 |
+
</p>
|
| 191 |
+
|
| 192 |
+
| Training | Deployment |
|
| 193 |
+
|---|---|
|
| 194 |
+
| Action and future normalized RGB patch targets are learned by one backbone | Encoded history conditions the action velocity field |
|
| 195 |
+
| Seven action steps are supervised | Seven actions are predicted |
|
| 196 |
+
| Future normalized RGB patch supervision is present | RGB patch tokens and the RGB prediction head remain outside the inference graph |
|
| 197 |
+
| The complete training objective is active | Three actions execute before replanning |
|
| 198 |
+
|
| 199 |
+
## Reference
|
| 200 |
+
|
| 201 |
+
<details>
|
| 202 |
+
<summary><strong>Full evaluation commands</strong></summary>
|
| 203 |
+
|
| 204 |
+
### One GPU
|
| 205 |
+
|
| 206 |
+
```bash
|
| 207 |
+
python eval.py \
|
| 208 |
+
--suite libero_spatial \
|
| 209 |
+
--gpus 1 \
|
| 210 |
+
--output-dir results/libero_spatial
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
### Multiple GPUs
|
| 214 |
+
|
| 215 |
+
```bash
|
| 216 |
+
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 python eval.py \
|
| 217 |
+
--suite libero_spatial \
|
| 218 |
+
--gpus 8 \
|
| 219 |
+
--output-dir results/libero_spatial_8gpu
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
Each GPU receives an independent progress bar. After all workers finish, rank 0
|
| 223 |
+
prints per task and overall success rates and writes a structured
|
| 224 |
+
`results.json`. Use a new output directory for each evaluation to preserve
|
| 225 |
+
earlier results.
|
| 226 |
+
|
| 227 |
+
Supported suites.
|
| 228 |
+
|
| 229 |
+
```text
|
| 230 |
+
libero_spatial
|
| 231 |
+
libero_object
|
| 232 |
+
libero_goal
|
| 233 |
+
libero_10
|
| 234 |
+
```
|
| 235 |
+
|
| 236 |
+
</details>
|
| 237 |
+
|
| 238 |
+
<details>
|
| 239 |
+
<summary><strong>Repository contents</strong></summary>
|
| 240 |
+
|
| 241 |
+
```text
|
| 242 |
+
.
|
| 243 |
+
├── checkpoints/
|
| 244 |
+
│ ├── libero_10/model.safetensors
|
| 245 |
+
│ ├── libero_goal/model.safetensors
|
| 246 |
+
│ ├── libero_object/model.safetensors
|
| 247 |
+
│ └── libero_spatial/model.safetensors
|
| 248 |
+
├── dependencies/
|
| 249 |
+
│ ├── ViT-B-32.pt
|
| 250 |
+
│ └── mae_pretrain_vit_base.pth
|
| 251 |
+
├── eval.py
|
| 252 |
+
├── inference.py
|
| 253 |
+
├── config.json
|
| 254 |
+
└── requirements.txt
|
| 255 |
+
```
|
| 256 |
+
|
| 257 |
+
`dependencies/` contains the frozen visual and language encoder weights needed
|
| 258 |
+
by the released WorldDiT runtime. The repository contains every model weight
|
| 259 |
+
required for inference.
|
| 260 |
+
|
| 261 |
+
</details>
|
| 262 |
+
|
| 263 |
+
<details>
|
| 264 |
+
<summary><strong>Inference API and tensor shapes</strong></summary>
|
| 265 |
+
|
| 266 |
+
```python
|
| 267 |
+
from inference import load_model
|
| 268 |
+
|
| 269 |
+
model = load_model(".", suite="libero_spatial", device="cuda")
|
| 270 |
+
actions = model(primary_images, wrist_images, robot_state, text_tokens)
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
| Input or output | Shape |
|
| 274 |
+
|---|---|
|
| 275 |
+
| Primary-camera images | `[B, 3, 3, 224, 224]` |
|
| 276 |
+
| Wrist-camera images | `[B, 3, 3, 224, 224]` |
|
| 277 |
+
| Robot state | `[B, 3, 8]` |
|
| 278 |
+
| OpenAI CLIP text tokens | `[B, 3, 77]` |
|
| 279 |
+
| Predicted action tensor | `[B, 3, 7, 7]` |
|
| 280 |
+
|
| 281 |
+
Evaluation uses the final temporal slot of the predicted action tensor.
|
| 282 |
+
|
| 283 |
+
</details>
|
| 284 |
+
|
| 285 |
+
<details>
|
| 286 |
+
<summary><strong>Architecture details</strong></summary>
|
| 287 |
+
|
| 288 |
+
| Component | Specification |
|
| 289 |
+
|---|---|
|
| 290 |
+
| Backbone | WorldDiT diffusion transformer |
|
| 291 |
+
| Observation context | 3 observation steps |
|
| 292 |
+
| Action horizon | 7 actions |
|
| 293 |
+
| Action dimension | 7 |
|
| 294 |
+
| Action aggregation | Temporal ensembling |
|
| 295 |
+
| Language encoder | OpenAI CLIP ViT-B/32 |
|
| 296 |
+
| Visual encoder | MAE ViT-B |
|
| 297 |
+
| Evaluation | Headless LIBERO with EGL |
|
| 298 |
+
| Checkpoint format | SafeTensors |
|
| 299 |
+
|
| 300 |
+
</details>
|
| 301 |
+
|
| 302 |
+
## Use and scope
|
| 303 |
+
|
| 304 |
+
| Intended use | Scope of the release |
|
| 305 |
+
|---|---|
|
| 306 |
+
| Research on world and action modeling for language conditioned robot manipulation. The architecture supports continuous action generation with auxiliary future normalized RGB patch prediction. | The current release evaluates WorldDiT in LIBERO simulation under the released protocol and provides checkpoints for all four suites. |
|
| 307 |
+
| Architecture research, reproduction, and evaluation of multimodal diffusion backbones for robot manipulation. | Real robot reliability, safety, and transfer across embodiments require dedicated future evaluation. The present experiments evaluate the integrated WorldDiT system. Targeted ablations are required to attribute performance to the future normalized RGB patch objective. Total instantiated parameter count characterizes model scale. Training cost, deployment latency, and runtime efficiency require dedicated measurements. |
|
| 308 |
+
|
| 309 |
+
## Citation
|
| 310 |
+
|
| 311 |
+
If you use WorldDiT in your research, please cite the paper.
|
| 312 |
+
|
| 313 |
+
```bibtex
|
| 314 |
+
@article{260723909,
|
| 315 |
+
title={{WorldDiT: A Unified Diffusion Architecture for World and Action Modeling}},
|
| 316 |
+
author={Sen Wang and R. Gnana Praveen and Bidhan Roy and Marcos Villagra},
|
| 317 |
+
year={{2026}},
|
| 318 |
+
eprint={{2607.23909}},
|
| 319 |
+
archivePrefix={{arXiv}}
|
| 320 |
+
}
|
| 321 |
+
```
|
| 322 |
+
|
| 323 |
+
## License
|
| 324 |
+
|
| 325 |
+
The WorldDiT checkpoints, model card, and original release materials are
|
| 326 |
+
licensed under [Creative Commons Attribution 4.0
|
| 327 |
+
International](https://creativecommons.org/licenses/by/4.0/). You may copy,
|
| 328 |
+
redistribute, and adapt them, including commercially, with appropriate credit
|
| 329 |
+
to Bagel Labs and the WorldDiT authors, a link to the license, and an indication
|
| 330 |
+
of any changes. Third party dependencies and assets remain governed by their
|
| 331 |
+
upstream licenses.
|
| 332 |
+
|
| 333 |
+
## Authors and contact
|
| 334 |
+
|
| 335 |
+
WorldDiT is developed by Sen Wang, Praveen Rajasekhar, Bidhan Roy, and Marcos
|
| 336 |
+
Villagra at Bagel Labs. Questions can be sent to research@bagel.com.
|
| 337 |
+
|
| 338 |
+
## Acknowledgments
|
| 339 |
+
|
| 340 |
+
This release builds on
|
| 341 |
+
[LIBERO](https://github.com/Lifelong-Robot-Learning/LIBERO),
|
| 342 |
+
[robosuite](https://github.com/ARISE-Initiative/robosuite),
|
| 343 |
+
[OpenAI CLIP](https://github.com/openai/CLIP), and
|
| 344 |
+
[Masked Autoencoders](https://github.com/facebookresearch/mae). Third party
|
| 345 |
+
components remain subject to their respective upstream terms.
|
| 346 |
+
|
| 347 |
+
---
|
| 348 |
+
|
| 349 |
+
<div style="display: flex; align-items: center; gap: 8px;">
|
| 350 |
+
<span>Made with ❤️ by</span>
|
| 351 |
+
<a href="https://twitter.com/bageldotcom" target="_blank">
|
| 352 |
+
<img src="https://img.shields.io/badge/Bagel_Labs-1DA1F2?style=for-the-badge&logo=twitter&logoColor=white" alt="Follow Bagel Labs on Twitter" height="28">
|
| 353 |
+
</a>
|
| 354 |
+
</div>
|
checkpoints/libero_10/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c0fa3dd7597949d53dd98bdaec28a2267459be6bd1bebc6d9b2c0d82095529d8
|
| 3 |
+
size 540446188
|
checkpoints/libero_goal/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5da34e96382fe831437459e29608d9db57356e2d1b9149cb20888cda72e6bdac
|
| 3 |
+
size 540446196
|
checkpoints/libero_object/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8c7d50e2038d7e7aea87268770007fc4f6a639158002b57ab573c32fb218b97d
|
| 3 |
+
size 540446196
|
checkpoints/libero_spatial/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d2ddd556c1dc886dbc8927c6e53ad7c2df60dc6ff14b164753ca77cecbec9707
|
| 3 |
+
size 540446196
|
config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "worlddit",
|
| 3 |
+
"architectures": ["WorldDiTPolicy"],
|
| 4 |
+
"context_frames": 3,
|
| 5 |
+
"action_horizon": 7,
|
| 6 |
+
"suites": ["libero_10", "libero_spatial", "libero_goal", "libero_object"],
|
| 7 |
+
"runtime": {
|
| 8 |
+
"model": "inference.py",
|
| 9 |
+
"evaluation": "eval.py"
|
| 10 |
+
}
|
| 11 |
+
}
|
dependencies/ViT-B-32.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:40d365715913c9da98579312b702a82c18be219cc2a73407c4526f58eba950af
|
| 3 |
+
size 353976522
|
dependencies/mae_pretrain_vit_base.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aec5f0b68e5f3193a00b07bc65a37440db549c15b36b8bea242606cc40c4bc5d
|
| 3 |
+
size 343249461
|
eval.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Evaluate a released WorldDiT checkpoint on LIBERO."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import copy
|
| 8 |
+
import contextlib
|
| 9 |
+
import json
|
| 10 |
+
import math
|
| 11 |
+
import os
|
| 12 |
+
import random
|
| 13 |
+
import subprocess
|
| 14 |
+
import sys
|
| 15 |
+
from collections import deque
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
|
| 18 |
+
import clip
|
| 19 |
+
import numpy as np
|
| 20 |
+
import torch
|
| 21 |
+
from PIL import Image
|
| 22 |
+
from scipy.spatial.transform import Rotation
|
| 23 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 24 |
+
from tqdm.auto import tqdm
|
| 25 |
+
|
| 26 |
+
from inference import ACTION_HORIZON, CONTEXT_STEPS, SUITES, load_model
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def rank():
|
| 30 |
+
return int(os.environ.get("RANK", "0"))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def world_size():
|
| 34 |
+
return int(os.environ.get("WORLD_SIZE", "1"))
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def launch(gpus: int):
|
| 38 |
+
if not 1 <= gpus <= torch.cuda.device_count():
|
| 39 |
+
raise ValueError(f"--gpus must be between 1 and {torch.cuda.device_count()}")
|
| 40 |
+
command = [
|
| 41 |
+
sys.executable,
|
| 42 |
+
"-m",
|
| 43 |
+
"torch.distributed.run",
|
| 44 |
+
"--standalone",
|
| 45 |
+
f"--nproc_per_node={gpus}",
|
| 46 |
+
str(Path(__file__).resolve()),
|
| 47 |
+
*sys.argv[1:],
|
| 48 |
+
]
|
| 49 |
+
environment = os.environ.copy()
|
| 50 |
+
environment.setdefault("OMP_NUM_THREADS", "1")
|
| 51 |
+
return subprocess.run(command, check=False, env=environment).returncode
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def configure_libero(libero_root: Path, output: Path):
|
| 55 |
+
package = libero_root / "libero" / "libero"
|
| 56 |
+
required = (package / "bddl_files", package / "init_files", package / "assets")
|
| 57 |
+
if any(not path.is_dir() for path in required):
|
| 58 |
+
raise FileNotFoundError(f"invalid LIBERO checkout: {libero_root}")
|
| 59 |
+
config_dir = output / "libero_config"
|
| 60 |
+
if rank() == 0:
|
| 61 |
+
output.mkdir(parents=True, exist_ok=False)
|
| 62 |
+
config_dir.mkdir()
|
| 63 |
+
paths = {
|
| 64 |
+
"assets": str(package / "assets"),
|
| 65 |
+
"bddl_files": str(package / "bddl_files"),
|
| 66 |
+
"benchmark_root": str(package),
|
| 67 |
+
"datasets": str(package.parent / "datasets"),
|
| 68 |
+
"init_states": str(package / "init_files"),
|
| 69 |
+
}
|
| 70 |
+
(config_dir / "config.yaml").write_text(json.dumps(paths), encoding="utf-8")
|
| 71 |
+
torch.distributed.barrier()
|
| 72 |
+
os.environ["LIBERO_CONFIG_PATH"] = str(config_dir)
|
| 73 |
+
sys.path.insert(0, str(libero_root))
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def orientation(quaternion):
|
| 77 |
+
quaternion = np.asarray(quaternion, dtype=np.float64).copy()
|
| 78 |
+
quaternion[3] = np.clip(quaternion[3], -1.0, 1.0)
|
| 79 |
+
denominator = np.sqrt(1.0 - quaternion[3] ** 2)
|
| 80 |
+
axisangle = (
|
| 81 |
+
np.zeros(3)
|
| 82 |
+
if math.isclose(denominator, 0.0)
|
| 83 |
+
else quaternion[:3] * 2.0 * math.acos(quaternion[3]) / denominator
|
| 84 |
+
)
|
| 85 |
+
return Rotation.from_euler("xyz", axisangle).as_euler("xyz")
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def finish_action(action: torch.Tensor):
|
| 89 |
+
action = action.detach().cpu().numpy().copy()
|
| 90 |
+
action[-1] = 1.0 if action[-1] > 0.5 else -1.0
|
| 91 |
+
return action
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class PolicyRunner:
|
| 95 |
+
def __init__(
|
| 96 |
+
self, model, temperature: float, execution_horizon: int, max_steps: int
|
| 97 |
+
):
|
| 98 |
+
self.model = model
|
| 99 |
+
self.processor = getattr(model, "module", model).image_processor
|
| 100 |
+
self.temperature = temperature
|
| 101 |
+
self.execution_horizon = execution_horizon
|
| 102 |
+
self.max_steps = max_steps
|
| 103 |
+
|
| 104 |
+
def reset(self):
|
| 105 |
+
self.primary = deque(maxlen=CONTEXT_STEPS)
|
| 106 |
+
self.wrist = deque(maxlen=CONTEXT_STEPS)
|
| 107 |
+
self.state = deque(maxlen=CONTEXT_STEPS)
|
| 108 |
+
self.pending = deque()
|
| 109 |
+
self.predictions = torch.zeros(
|
| 110 |
+
self.max_steps, self.max_steps + ACTION_HORIZON, 7, device="cuda"
|
| 111 |
+
)
|
| 112 |
+
self.valid = torch.zeros(
|
| 113 |
+
self.max_steps,
|
| 114 |
+
self.max_steps + ACTION_HORIZON,
|
| 115 |
+
dtype=torch.bool,
|
| 116 |
+
device="cuda",
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
def observe(self, observation):
|
| 120 |
+
primary = Image.fromarray(observation["agentview_image"][::-1])
|
| 121 |
+
wrist = Image.fromarray(observation["robot0_eye_in_hand_image"])
|
| 122 |
+
self.primary.append(self.processor(primary).unsqueeze(0).unsqueeze(0))
|
| 123 |
+
self.wrist.append(self.processor(wrist).unsqueeze(0).unsqueeze(0))
|
| 124 |
+
state = np.concatenate(
|
| 125 |
+
(
|
| 126 |
+
observation["robot0_eef_pos"],
|
| 127 |
+
orientation(observation["robot0_eef_quat"]),
|
| 128 |
+
observation["robot0_gripper_qpos"],
|
| 129 |
+
)
|
| 130 |
+
)
|
| 131 |
+
self.state.append(torch.from_numpy(state).float().view(1, 1, -1))
|
| 132 |
+
|
| 133 |
+
def prefill(self, observations):
|
| 134 |
+
for observation in observations[-CONTEXT_STEPS:-1]:
|
| 135 |
+
self.observe(observation)
|
| 136 |
+
|
| 137 |
+
def ensemble(self, chunk: torch.Tensor, timestep: int):
|
| 138 |
+
self.predictions[timestep, timestep : timestep + ACTION_HORIZON] = chunk
|
| 139 |
+
self.valid[timestep, timestep : timestep + ACTION_HORIZON] = True
|
| 140 |
+
selected = []
|
| 141 |
+
for target in range(timestep, timestep + self.execution_horizon):
|
| 142 |
+
mask = self.valid[: timestep + 1, target]
|
| 143 |
+
actions = self.predictions[: timestep + 1, target][mask]
|
| 144 |
+
weights = np.exp(-self.temperature * np.arange(len(actions)))
|
| 145 |
+
weights = torch.as_tensor(weights / weights.sum(), device="cuda").unsqueeze(
|
| 146 |
+
1
|
| 147 |
+
)
|
| 148 |
+
selected.append(finish_action((actions * weights).sum(0)))
|
| 149 |
+
self.pending.extend(selected[1:])
|
| 150 |
+
return selected[0]
|
| 151 |
+
|
| 152 |
+
@torch.inference_mode()
|
| 153 |
+
def act(self, observation, instruction: str, timestep: int):
|
| 154 |
+
self.observe(observation)
|
| 155 |
+
if self.pending:
|
| 156 |
+
return self.pending.popleft()
|
| 157 |
+
if len(self.primary) != CONTEXT_STEPS:
|
| 158 |
+
raise RuntimeError("evaluation requires three real context observations")
|
| 159 |
+
primary = torch.cat(tuple(self.primary), dim=1).cuda()
|
| 160 |
+
wrist = torch.cat(tuple(self.wrist), dim=1).cuda()
|
| 161 |
+
state = torch.cat(tuple(self.state), dim=1).cuda()
|
| 162 |
+
text = (
|
| 163 |
+
clip.tokenize([instruction] * CONTEXT_STEPS, truncate=True)
|
| 164 |
+
.view(1, CONTEXT_STEPS, -1)
|
| 165 |
+
.cuda()
|
| 166 |
+
)
|
| 167 |
+
chunk = self.model(primary, wrist, state, text)[0, -1]
|
| 168 |
+
return self.ensemble(chunk, timestep)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def evaluate(args, model):
|
| 172 |
+
with (
|
| 173 |
+
open(os.devnull, "w") as quiet,
|
| 174 |
+
contextlib.redirect_stdout(quiet),
|
| 175 |
+
contextlib.redirect_stderr(quiet),
|
| 176 |
+
):
|
| 177 |
+
from libero.libero import benchmark
|
| 178 |
+
from libero.libero.envs import OffScreenRenderEnv
|
| 179 |
+
|
| 180 |
+
suite = benchmark.get_benchmark_dict()[args.suite]()
|
| 181 |
+
runner = PolicyRunner(
|
| 182 |
+
model, args.temperature, args.execution_horizon, args.max_steps
|
| 183 |
+
)
|
| 184 |
+
total = args.tasks * args.episodes
|
| 185 |
+
assigned = list(range(total))[rank() :: world_size()]
|
| 186 |
+
local_results = []
|
| 187 |
+
progress = tqdm(
|
| 188 |
+
assigned,
|
| 189 |
+
desc=f"GPU {rank()}",
|
| 190 |
+
position=rank(),
|
| 191 |
+
dynamic_ncols=True,
|
| 192 |
+
leave=True,
|
| 193 |
+
)
|
| 194 |
+
for evaluation_id in progress:
|
| 195 |
+
task_id, episode_index = divmod(evaluation_id, args.episodes)
|
| 196 |
+
episode_id = args.episode_offset + episode_index
|
| 197 |
+
task = suite.get_task(task_id)
|
| 198 |
+
bddl = (
|
| 199 |
+
args.libero_path
|
| 200 |
+
/ "libero"
|
| 201 |
+
/ "libero"
|
| 202 |
+
/ "bddl_files"
|
| 203 |
+
/ task.problem_folder
|
| 204 |
+
/ task.bddl_file
|
| 205 |
+
)
|
| 206 |
+
environment = OffScreenRenderEnv(
|
| 207 |
+
bddl_file_name=str(bddl),
|
| 208 |
+
camera_heights=128,
|
| 209 |
+
camera_widths=128,
|
| 210 |
+
render_gpu_device_id=int(os.environ["LOCAL_RANK"]),
|
| 211 |
+
)
|
| 212 |
+
try:
|
| 213 |
+
environment.reset()
|
| 214 |
+
environment.seed(66)
|
| 215 |
+
initial_states = torch.load(
|
| 216 |
+
args.libero_path
|
| 217 |
+
/ "libero"
|
| 218 |
+
/ "libero"
|
| 219 |
+
/ "init_files"
|
| 220 |
+
/ task.problem_folder
|
| 221 |
+
/ task.init_states_file,
|
| 222 |
+
weights_only=False,
|
| 223 |
+
)
|
| 224 |
+
if episode_id >= len(initial_states):
|
| 225 |
+
raise IndexError(
|
| 226 |
+
f"episode {episode_id} is unavailable for task {task_id}"
|
| 227 |
+
)
|
| 228 |
+
observation = environment.set_init_state(initial_states[episode_id])
|
| 229 |
+
warmup = []
|
| 230 |
+
for _ in range(5):
|
| 231 |
+
observation, _, _, _ = environment.step(np.zeros(7))
|
| 232 |
+
warmup.append(copy.deepcopy(observation))
|
| 233 |
+
runner.reset()
|
| 234 |
+
runner.prefill(warmup)
|
| 235 |
+
observation = warmup[-1]
|
| 236 |
+
success = 0
|
| 237 |
+
for steps in range(1, args.max_steps + 1):
|
| 238 |
+
action = runner.act(observation, task.language, steps - 1)
|
| 239 |
+
observation, _, done, _ = environment.step(action)
|
| 240 |
+
if done:
|
| 241 |
+
success = 1
|
| 242 |
+
break
|
| 243 |
+
local_results.append((evaluation_id, task_id, episode_id, success, steps))
|
| 244 |
+
progress.set_postfix(successes=sum(item[3] for item in local_results))
|
| 245 |
+
finally:
|
| 246 |
+
environment.close()
|
| 247 |
+
|
| 248 |
+
gathered = [None] * world_size() if rank() == 0 else None
|
| 249 |
+
torch.distributed.gather_object(local_results, gathered, dst=0)
|
| 250 |
+
if rank() != 0:
|
| 251 |
+
return
|
| 252 |
+
results = sorted((item for group in gathered for item in group), key=lambda x: x[0])
|
| 253 |
+
per_task = []
|
| 254 |
+
print()
|
| 255 |
+
for task_id in range(args.tasks):
|
| 256 |
+
values = [item[3] for item in results if item[1] == task_id]
|
| 257 |
+
rate = float(np.mean(values))
|
| 258 |
+
per_task.append(rate)
|
| 259 |
+
print(f"Task {task_id}: {sum(values)}/{len(values)} ({rate:.1%})")
|
| 260 |
+
successes = sum(item[3] for item in results)
|
| 261 |
+
print(f"Overall: {successes}/{len(results)} ({successes / len(results):.1%})")
|
| 262 |
+
report = {
|
| 263 |
+
"suite": args.suite,
|
| 264 |
+
"gpus": world_size(),
|
| 265 |
+
"episodes": len(results),
|
| 266 |
+
"successes": successes,
|
| 267 |
+
"success_rate": successes / len(results),
|
| 268 |
+
"per_task_success_rate": per_task,
|
| 269 |
+
"results": [
|
| 270 |
+
{"task": item[1], "episode": item[2], "success": item[3], "steps": item[4]}
|
| 271 |
+
for item in results
|
| 272 |
+
],
|
| 273 |
+
}
|
| 274 |
+
(args.output_dir / "results.json").write_text(
|
| 275 |
+
json.dumps(report, indent=2) + "\n", encoding="utf-8"
|
| 276 |
+
)
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def parser():
|
| 280 |
+
value = argparse.ArgumentParser(description=__doc__)
|
| 281 |
+
value.add_argument("--suite", required=True, choices=SUITES)
|
| 282 |
+
value.add_argument("--gpus", type=int, default=1)
|
| 283 |
+
value.add_argument(
|
| 284 |
+
"--model-root", type=Path, default=Path(__file__).resolve().parent
|
| 285 |
+
)
|
| 286 |
+
value.add_argument(
|
| 287 |
+
"--libero-path", type=Path, default=Path("~/LIBERO").expanduser()
|
| 288 |
+
)
|
| 289 |
+
value.add_argument("--output-dir", type=Path, required=True)
|
| 290 |
+
value.add_argument("--tasks", type=int, default=10)
|
| 291 |
+
value.add_argument("--episodes", type=int, default=50)
|
| 292 |
+
value.add_argument("--episode-offset", type=int, default=0)
|
| 293 |
+
value.add_argument("--max-steps", type=int, default=600)
|
| 294 |
+
value.add_argument("--execution-horizon", type=int, choices=(1, 3), default=3)
|
| 295 |
+
value.add_argument("--temperature", type=float, default=0.01)
|
| 296 |
+
return value
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def main():
|
| 300 |
+
args = parser().parse_args()
|
| 301 |
+
if "RANK" not in os.environ:
|
| 302 |
+
return launch(args.gpus)
|
| 303 |
+
if args.gpus != world_size():
|
| 304 |
+
raise ValueError(
|
| 305 |
+
f"--gpus={args.gpus} but torchrun started {world_size()} workers"
|
| 306 |
+
)
|
| 307 |
+
args.model_root = args.model_root.expanduser().resolve()
|
| 308 |
+
args.libero_path = args.libero_path.expanduser().resolve()
|
| 309 |
+
args.output_dir = args.output_dir.expanduser().resolve()
|
| 310 |
+
os.environ.update(MUJOCO_GL="egl", PYOPENGL_PLATFORM="egl")
|
| 311 |
+
os.environ.setdefault("NCCL_IB_DISABLE", "1")
|
| 312 |
+
os.environ.setdefault("NCCL_P2P_DISABLE", "1")
|
| 313 |
+
os.environ.setdefault("NCCL_CUMEM_ENABLE", "0")
|
| 314 |
+
os.environ.setdefault("TORCH_NCCL_BLOCKING_WAIT", "1")
|
| 315 |
+
torch.cuda.set_device(int(os.environ["LOCAL_RANK"]))
|
| 316 |
+
torch.distributed.init_process_group(
|
| 317 |
+
backend="nccl", device_id=torch.device("cuda", int(os.environ["LOCAL_RANK"]))
|
| 318 |
+
)
|
| 319 |
+
try:
|
| 320 |
+
configure_libero(args.libero_path, args.output_dir)
|
| 321 |
+
model = load_model(args.model_root, args.suite, torch.device("cuda"))
|
| 322 |
+
seed = 66 + rank()
|
| 323 |
+
random.seed(seed)
|
| 324 |
+
np.random.seed(seed)
|
| 325 |
+
torch.manual_seed(seed)
|
| 326 |
+
model = DDP(
|
| 327 |
+
model,
|
| 328 |
+
device_ids=[int(os.environ["LOCAL_RANK"])],
|
| 329 |
+
find_unused_parameters=True,
|
| 330 |
+
)
|
| 331 |
+
model.eval()
|
| 332 |
+
evaluate(args, model)
|
| 333 |
+
torch.distributed.barrier()
|
| 334 |
+
finally:
|
| 335 |
+
torch.distributed.destroy_process_group()
|
| 336 |
+
return 0
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
if __name__ == "__main__":
|
| 340 |
+
raise SystemExit(main())
|
inference.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compact inference-only WorldDiT runtime for the released LIBERO checkpoints."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
from functools import partial
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import clip
|
| 10 |
+
import torch
|
| 11 |
+
from einops import rearrange, repeat
|
| 12 |
+
from einops_exts import rearrange_many
|
| 13 |
+
from safetensors import safe_open
|
| 14 |
+
from timm.models.vision_transformer import Block, PatchEmbed
|
| 15 |
+
from torch import einsum, nn
|
| 16 |
+
|
| 17 |
+
SUITES = ("libero_10", "libero_spatial", "libero_goal", "libero_object")
|
| 18 |
+
CONTEXT_STEPS = 3
|
| 19 |
+
ACTION_HORIZON = 7
|
| 20 |
+
HIDDEN_DIM = 1024
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class VisionEncoder(nn.Module):
|
| 24 |
+
"""The encoder half of the frozen MAE dependency."""
|
| 25 |
+
|
| 26 |
+
def __init__(self):
|
| 27 |
+
super().__init__()
|
| 28 |
+
self.patch_embed = PatchEmbed(224, 16, 3, 768)
|
| 29 |
+
self.cls_token = nn.Parameter(torch.zeros(1, 1, 768))
|
| 30 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, 197, 768), requires_grad=False)
|
| 31 |
+
self.blocks = nn.ModuleList(
|
| 32 |
+
[
|
| 33 |
+
Block(
|
| 34 |
+
768,
|
| 35 |
+
12,
|
| 36 |
+
4,
|
| 37 |
+
qkv_bias=True,
|
| 38 |
+
norm_layer=partial(nn.LayerNorm, eps=1e-6),
|
| 39 |
+
)
|
| 40 |
+
for _ in range(12)
|
| 41 |
+
]
|
| 42 |
+
)
|
| 43 |
+
self.norm = nn.LayerNorm(768, eps=1e-6)
|
| 44 |
+
|
| 45 |
+
def forward(self, images: torch.Tensor) -> torch.Tensor:
|
| 46 |
+
patches = self.patch_embed(images) + self.pos_embed[:, 1:]
|
| 47 |
+
# Retain the released encoder's mask_ratio=0 behavior, including RNG use.
|
| 48 |
+
order = torch.rand(patches.shape[:2], device=patches.device).argsort(dim=1)
|
| 49 |
+
patches = torch.gather(patches, 1, order.unsqueeze(-1).expand_as(patches))
|
| 50 |
+
cls = (self.cls_token + self.pos_embed[:, :1]).expand(images.shape[0], -1, -1)
|
| 51 |
+
tokens = torch.cat((cls, patches), dim=1)
|
| 52 |
+
for block in self.blocks:
|
| 53 |
+
tokens = block(tokens)
|
| 54 |
+
return self.norm(tokens)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class PerceiverAttention(nn.Module):
|
| 58 |
+
def __init__(self, dim: int = 768, dim_head: int = 64, heads: int = 8):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.scale = dim_head**-0.5
|
| 61 |
+
self.heads = heads
|
| 62 |
+
inner = dim_head * heads
|
| 63 |
+
self.norm_media = nn.LayerNorm(dim)
|
| 64 |
+
self.norm_latents = nn.LayerNorm(dim)
|
| 65 |
+
self.to_q = nn.Linear(dim, inner, bias=False)
|
| 66 |
+
self.to_kv = nn.Linear(dim, inner * 2, bias=False)
|
| 67 |
+
self.to_out = nn.Linear(inner, dim, bias=False)
|
| 68 |
+
|
| 69 |
+
def forward(self, media: torch.Tensor, latents: torch.Tensor) -> torch.Tensor:
|
| 70 |
+
media, latents = self.norm_media(media), self.norm_latents(latents)
|
| 71 |
+
query = self.to_q(latents)
|
| 72 |
+
key, value = self.to_kv(torch.cat((media, latents), dim=-2)).chunk(2, dim=-1)
|
| 73 |
+
query, key, value = rearrange_many(
|
| 74 |
+
(query, key, value), "b t n (h d) -> b h t n d", h=self.heads
|
| 75 |
+
)
|
| 76 |
+
scores = einsum("... i d, ... j d -> ... i j", query * self.scale, key)
|
| 77 |
+
weights = (scores - scores.amax(dim=-1, keepdim=True).detach()).softmax(-1)
|
| 78 |
+
output = einsum("... i j, ... j d -> ... i d", weights, value)
|
| 79 |
+
return self.to_out(rearrange(output, "b h t n d -> b t n (h d)"))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class PerceiverResampler(nn.Module):
|
| 83 |
+
def __init__(self):
|
| 84 |
+
super().__init__()
|
| 85 |
+
self.latents = nn.Parameter(torch.randn(16, 768))
|
| 86 |
+
self.layers = nn.ModuleList()
|
| 87 |
+
for _ in range(3):
|
| 88 |
+
feed_forward = nn.Sequential(
|
| 89 |
+
nn.LayerNorm(768),
|
| 90 |
+
nn.Linear(768, 3072, bias=False),
|
| 91 |
+
nn.GELU(),
|
| 92 |
+
nn.Linear(3072, 768, bias=False),
|
| 93 |
+
)
|
| 94 |
+
self.layers.append(nn.ModuleList((PerceiverAttention(), feed_forward)))
|
| 95 |
+
self.norm = nn.LayerNorm(768)
|
| 96 |
+
|
| 97 |
+
def forward(self, tokens: torch.Tensor) -> torch.Tensor:
|
| 98 |
+
batch, steps = tokens.shape[:2]
|
| 99 |
+
tokens = rearrange(tokens, "b t f v d -> b t (f v) d")
|
| 100 |
+
latents = repeat(self.latents, "n d -> b t n d", b=batch, t=steps)
|
| 101 |
+
for attention, feed_forward in self.layers:
|
| 102 |
+
latents = attention(tokens, latents) + latents
|
| 103 |
+
latents = feed_forward(latents) + latents
|
| 104 |
+
return self.norm(latents)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def _time_embedding(timestep: torch.Tensor, dim: int) -> torch.Tensor:
|
| 108 |
+
half = dim // 2
|
| 109 |
+
frequency = torch.exp(
|
| 110 |
+
-math.log(10000.0)
|
| 111 |
+
* torch.arange(half, device=timestep.device, dtype=timestep.dtype)
|
| 112 |
+
/ max(half - 1, 1)
|
| 113 |
+
)
|
| 114 |
+
phase = timestep.unsqueeze(-1) * frequency.unsqueeze(0) * 1000.0
|
| 115 |
+
embedding = torch.cat((torch.sin(phase), torch.cos(phase)), dim=-1)
|
| 116 |
+
return embedding if dim % 2 == 0 else torch.nn.functional.pad(embedding, (0, 1))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class MLP(nn.Module):
|
| 120 |
+
def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
|
| 121 |
+
super().__init__()
|
| 122 |
+
self.net = nn.Sequential(
|
| 123 |
+
nn.Linear(input_dim, hidden_dim),
|
| 124 |
+
nn.SiLU(),
|
| 125 |
+
nn.Linear(hidden_dim, output_dim),
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
def forward(self, value: torch.Tensor) -> torch.Tensor:
|
| 129 |
+
return self.net(value)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def _modulate(value: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor):
|
| 133 |
+
return value * (1.0 + scale) + shift
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
class DFDiTBlock(nn.Module):
|
| 137 |
+
def __init__(self):
|
| 138 |
+
super().__init__()
|
| 139 |
+
self.norm1 = nn.LayerNorm(HIDDEN_DIM, elementwise_affine=False)
|
| 140 |
+
self.attn = nn.MultiheadAttention(HIDDEN_DIM, 16, batch_first=True)
|
| 141 |
+
self.norm2 = nn.LayerNorm(HIDDEN_DIM, elementwise_affine=False)
|
| 142 |
+
self.mlp = nn.Sequential(
|
| 143 |
+
nn.Linear(HIDDEN_DIM, HIDDEN_DIM * 4),
|
| 144 |
+
nn.GELU(approximate="tanh"),
|
| 145 |
+
nn.Dropout(0.0),
|
| 146 |
+
nn.Linear(HIDDEN_DIM * 4, HIDDEN_DIM),
|
| 147 |
+
)
|
| 148 |
+
self.adaLN_modulation = nn.Sequential(
|
| 149 |
+
nn.SiLU(), nn.Linear(HIDDEN_DIM, 6 * HIDDEN_DIM)
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
def forward(self, tokens, modulation, mask):
|
| 153 |
+
shift_a, scale_a, gate_a, shift_m, scale_m, gate_m = self.adaLN_modulation(
|
| 154 |
+
modulation
|
| 155 |
+
).chunk(6, dim=-1)
|
| 156 |
+
attention_input = _modulate(self.norm1(tokens), shift_a, scale_a)
|
| 157 |
+
attention = self.attn(
|
| 158 |
+
attention_input,
|
| 159 |
+
attention_input,
|
| 160 |
+
attention_input,
|
| 161 |
+
attn_mask=mask,
|
| 162 |
+
need_weights=False,
|
| 163 |
+
)[0]
|
| 164 |
+
tokens = tokens + gate_a * attention
|
| 165 |
+
return tokens + gate_m * self.mlp(
|
| 166 |
+
_modulate(self.norm2(tokens), shift_m, scale_m)
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class ActionSampler(nn.Module):
|
| 171 |
+
"""Action sampler used by the released checkpoints."""
|
| 172 |
+
|
| 173 |
+
def __init__(self):
|
| 174 |
+
super().__init__()
|
| 175 |
+
self.context_norm = nn.LayerNorm(HIDDEN_DIM)
|
| 176 |
+
self.context_proj = nn.Linear(HIDDEN_DIM, HIDDEN_DIM)
|
| 177 |
+
self.action_tokenizer = MLP(7, HIDDEN_DIM * 4, HIDDEN_DIM)
|
| 178 |
+
self.action_decoder = MLP(HIDDEN_DIM, HIDDEN_DIM * 4, 7)
|
| 179 |
+
self.action_pos = nn.Parameter(
|
| 180 |
+
torch.randn(1, ACTION_HORIZON, HIDDEN_DIM) * 0.02
|
| 181 |
+
)
|
| 182 |
+
self.step_pos = nn.Parameter(
|
| 183 |
+
torch.randn(1, CONTEXT_STEPS, 1, HIDDEN_DIM) * 0.02
|
| 184 |
+
)
|
| 185 |
+
self.context_type = nn.Parameter(torch.randn(1, 1, HIDDEN_DIM) * 0.02)
|
| 186 |
+
self.action_type = nn.Parameter(torch.randn(1, 1, HIDDEN_DIM) * 0.02)
|
| 187 |
+
self.register_type = nn.Parameter(torch.randn(1, 1, HIDDEN_DIM) * 0.02)
|
| 188 |
+
self.register_tokens = nn.Parameter(torch.randn(1, 4, HIDDEN_DIM) * 0.02)
|
| 189 |
+
self.time_proj = nn.Sequential(
|
| 190 |
+
nn.Linear(HIDDEN_DIM, HIDDEN_DIM * 4),
|
| 191 |
+
nn.SiLU(),
|
| 192 |
+
nn.Linear(HIDDEN_DIM * 4, HIDDEN_DIM),
|
| 193 |
+
)
|
| 194 |
+
self.blocks = nn.ModuleList([DFDiTBlock() for _ in range(4)])
|
| 195 |
+
self.final_norm = nn.LayerNorm(HIDDEN_DIM)
|
| 196 |
+
|
| 197 |
+
def _times(self, timestep: torch.Tensor, count: int, dtype: torch.dtype):
|
| 198 |
+
embedded = self.time_proj(_time_embedding(timestep, HIDDEN_DIM).to(dtype))
|
| 199 |
+
return embedded.unsqueeze(1).expand(-1, count, -1)
|
| 200 |
+
|
| 201 |
+
@staticmethod
|
| 202 |
+
def _mask(steps: int, context_count: int, dtype, device):
|
| 203 |
+
targets, registers = ACTION_HORIZON, 4
|
| 204 |
+
block = context_count + targets + registers
|
| 205 |
+
mask = torch.full(
|
| 206 |
+
(steps * block, steps * block), -torch.inf, dtype=dtype, device=device
|
| 207 |
+
)
|
| 208 |
+
for step in range(steps):
|
| 209 |
+
start = step * block
|
| 210 |
+
context = slice(start, start + context_count)
|
| 211 |
+
action = slice(start + context_count, start + context_count + targets)
|
| 212 |
+
register = slice(start + context_count + targets, start + block)
|
| 213 |
+
for source_step in range(step + 1):
|
| 214 |
+
source = source_step * block
|
| 215 |
+
visible_context = slice(source, source + context_count)
|
| 216 |
+
mask[context, visible_context] = 0
|
| 217 |
+
mask[action, visible_context] = 0
|
| 218 |
+
mask[register, visible_context] = 0
|
| 219 |
+
mask[action, action] = 0
|
| 220 |
+
mask[action, register] = 0
|
| 221 |
+
mask[register, action] = 0
|
| 222 |
+
mask[register, register] = 0
|
| 223 |
+
return mask
|
| 224 |
+
|
| 225 |
+
def _velocity(self, context: torch.Tensor, noisy_action: torch.Tensor, timestep):
|
| 226 |
+
batch, steps, context_count, _ = context.shape
|
| 227 |
+
flat_batch = batch * steps
|
| 228 |
+
dtype, device = context.dtype, context.device
|
| 229 |
+
encoded_context = self.context_proj(self.context_norm(context))
|
| 230 |
+
encoded_context = encoded_context + self.context_type.to(dtype)
|
| 231 |
+
encoded_context = encoded_context + self.step_pos[:, :steps].to(dtype).expand(
|
| 232 |
+
-1, -1, context_count, -1
|
| 233 |
+
)
|
| 234 |
+
zero_time = torch.zeros(flat_batch, dtype=dtype, device=device)
|
| 235 |
+
context_mod = self._times(zero_time, context_count, dtype).view(
|
| 236 |
+
batch, steps, context_count, HIDDEN_DIM
|
| 237 |
+
) + self.context_type.to(dtype)
|
| 238 |
+
|
| 239 |
+
flat_action = noisy_action.reshape(flat_batch, ACTION_HORIZON, 7)
|
| 240 |
+
action_time = self._times(timestep, ACTION_HORIZON, dtype)
|
| 241 |
+
action = self.action_tokenizer(flat_action.to(dtype))
|
| 242 |
+
action = (
|
| 243 |
+
action
|
| 244 |
+
+ self.action_pos.to(dtype)
|
| 245 |
+
+ self.action_type.to(dtype)
|
| 246 |
+
+ action_time
|
| 247 |
+
)
|
| 248 |
+
action = action.view(batch, steps, ACTION_HORIZON, HIDDEN_DIM)
|
| 249 |
+
action = action + self.step_pos[:, :steps].to(dtype).expand(
|
| 250 |
+
-1, -1, ACTION_HORIZON, -1
|
| 251 |
+
)
|
| 252 |
+
action_mod = (action_time + self.action_type.to(dtype)).view(
|
| 253 |
+
batch, steps, ACTION_HORIZON, HIDDEN_DIM
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
registers = self.register_tokens.to(dtype).expand(batch, steps, -1, -1)
|
| 257 |
+
registers = registers + self.register_type.to(dtype)
|
| 258 |
+
registers = registers + self.step_pos[:, :steps].to(dtype).expand(-1, -1, 4, -1)
|
| 259 |
+
register_mod = self._times(zero_time, 4, dtype).view(
|
| 260 |
+
batch, steps, 4, HIDDEN_DIM
|
| 261 |
+
) + self.register_type.to(dtype)
|
| 262 |
+
|
| 263 |
+
tokens = torch.cat((encoded_context, action, registers), dim=2).flatten(1, 2)
|
| 264 |
+
modulation = torch.cat((context_mod, action_mod, register_mod), dim=2).flatten(
|
| 265 |
+
1, 2
|
| 266 |
+
)
|
| 267 |
+
mask = self._mask(steps, context_count, tokens.dtype, device)
|
| 268 |
+
for block in self.blocks:
|
| 269 |
+
tokens = block(tokens, modulation, mask)
|
| 270 |
+
tokens = self.final_norm(tokens).view(batch, steps, -1, HIDDEN_DIM)
|
| 271 |
+
return self.action_decoder(
|
| 272 |
+
tokens[:, :, context_count : context_count + ACTION_HORIZON]
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
def forward(self, context: torch.Tensor, sampling_steps: int = 20):
|
| 276 |
+
batch, steps = context.shape[:2]
|
| 277 |
+
dtype, device = self.context_norm.weight.dtype, context.device
|
| 278 |
+
context = context.to(dtype)
|
| 279 |
+
action = torch.randn(
|
| 280 |
+
batch, steps, ACTION_HORIZON, 7, dtype=dtype, device=device
|
| 281 |
+
)
|
| 282 |
+
for time in torch.linspace(0.0, 1.0, sampling_steps + 1, device=device)[:-1]:
|
| 283 |
+
timestep = torch.full(
|
| 284 |
+
(batch * steps,), float(time.item()), dtype=dtype, device=device
|
| 285 |
+
)
|
| 286 |
+
action = action + self._velocity(context, action, timestep) / sampling_steps
|
| 287 |
+
return action
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
class WorldDiTPolicy(nn.Module):
|
| 291 |
+
"""Released policy with only modules used by inference."""
|
| 292 |
+
|
| 293 |
+
def __init__(self, mae_path: Path, clip_path: Path):
|
| 294 |
+
super().__init__()
|
| 295 |
+
self.text_projector = nn.Linear(512, HIDDEN_DIM)
|
| 296 |
+
self.arm_state_encoder = nn.Linear(6, HIDDEN_DIM)
|
| 297 |
+
self.gripper_state_encoder = nn.Linear(2, HIDDEN_DIM)
|
| 298 |
+
self.state_projector = nn.Linear(HIDDEN_DIM * 2, HIDDEN_DIM)
|
| 299 |
+
self.vision_encoder = VisionEncoder()
|
| 300 |
+
self.perceiver_resampler = PerceiverResampler()
|
| 301 |
+
self.image_primary_projector = nn.Linear(768, HIDDEN_DIM)
|
| 302 |
+
self.cls_token_primary_projector = nn.Linear(768, HIDDEN_DIM)
|
| 303 |
+
self.image_wrist_projector = nn.Linear(768, HIDDEN_DIM)
|
| 304 |
+
self.cls_token_wrist_projector = nn.Linear(768, HIDDEN_DIM)
|
| 305 |
+
self.embedding_layer_norm = nn.LayerNorm(HIDDEN_DIM)
|
| 306 |
+
self.transformer_backbone_position_embedding = nn.Parameter(
|
| 307 |
+
torch.zeros(1, CONTEXT_STEPS, 1, HIDDEN_DIM)
|
| 308 |
+
)
|
| 309 |
+
self.unified_action_world_head = ActionSampler()
|
| 310 |
+
|
| 311 |
+
mae = torch.load(mae_path, map_location="cpu", weights_only=False)
|
| 312 |
+
self.vision_encoder.load_state_dict(mae["model"], strict=False)
|
| 313 |
+
self.clip_model, self.image_processor = clip.load(str(clip_path), device="cpu")
|
| 314 |
+
self.vision_encoder.requires_grad_(False)
|
| 315 |
+
self.clip_model.requires_grad_(False)
|
| 316 |
+
|
| 317 |
+
def _images(self, primary: torch.Tensor, wrist: torch.Tensor):
|
| 318 |
+
batch, steps = primary.shape[:2]
|
| 319 |
+
vision_dtype = next(self.vision_encoder.parameters()).dtype
|
| 320 |
+
with torch.no_grad():
|
| 321 |
+
primary_tokens = self.vision_encoder(primary.flatten(0, 1).to(vision_dtype))
|
| 322 |
+
wrist_tokens = self.vision_encoder(wrist.flatten(0, 1).to(vision_dtype))
|
| 323 |
+
cls_primary = primary_tokens[:, :1]
|
| 324 |
+
cls_wrist = wrist_tokens[:, :1]
|
| 325 |
+
resampler_dtype = next(self.perceiver_resampler.parameters()).dtype
|
| 326 |
+
cls_primary = cls_primary.to(resampler_dtype)
|
| 327 |
+
cls_wrist = cls_wrist.to(resampler_dtype)
|
| 328 |
+
primary_tokens = primary_tokens[:, 1:].to(resampler_dtype)
|
| 329 |
+
wrist_tokens = wrist_tokens[:, 1:].to(resampler_dtype)
|
| 330 |
+
primary_latents = self.perceiver_resampler(
|
| 331 |
+
primary_tokens.reshape(batch * steps, 196, 768).unsqueeze(1).unsqueeze(1)
|
| 332 |
+
)
|
| 333 |
+
wrist_latents = self.perceiver_resampler(
|
| 334 |
+
wrist_tokens.reshape(batch * steps, 196, 768).unsqueeze(1).unsqueeze(1)
|
| 335 |
+
)
|
| 336 |
+
images = torch.cat(
|
| 337 |
+
(
|
| 338 |
+
self.image_primary_projector(primary_latents.flatten(0, 2)).view(
|
| 339 |
+
batch, steps, 16, HIDDEN_DIM
|
| 340 |
+
),
|
| 341 |
+
self.image_wrist_projector(wrist_latents.flatten(0, 2)).view(
|
| 342 |
+
batch, steps, 16, HIDDEN_DIM
|
| 343 |
+
),
|
| 344 |
+
),
|
| 345 |
+
dim=2,
|
| 346 |
+
)
|
| 347 |
+
cls = torch.cat(
|
| 348 |
+
(
|
| 349 |
+
self.cls_token_primary_projector(cls_primary).view(
|
| 350 |
+
batch, steps, 1, HIDDEN_DIM
|
| 351 |
+
),
|
| 352 |
+
self.cls_token_wrist_projector(cls_wrist).view(
|
| 353 |
+
batch, steps, 1, HIDDEN_DIM
|
| 354 |
+
),
|
| 355 |
+
),
|
| 356 |
+
dim=2,
|
| 357 |
+
)
|
| 358 |
+
return images, cls
|
| 359 |
+
|
| 360 |
+
def forward(self, image_primary, image_wrist, state, text_token):
|
| 361 |
+
batch, steps = state.shape[:2]
|
| 362 |
+
if steps != CONTEXT_STEPS:
|
| 363 |
+
raise ValueError(f"expected {CONTEXT_STEPS} context frames, got {steps}")
|
| 364 |
+
with torch.no_grad():
|
| 365 |
+
text = self.clip_model.encode_text(text_token.flatten(0, 1)).type_as(state)
|
| 366 |
+
text = self.text_projector(text).view(batch, steps, 1, HIDDEN_DIM)
|
| 367 |
+
flat_state = state.flatten(0, 1)
|
| 368 |
+
state_token = self.state_projector(
|
| 369 |
+
torch.cat(
|
| 370 |
+
(
|
| 371 |
+
self.arm_state_encoder(flat_state[:, :6]),
|
| 372 |
+
self.gripper_state_encoder(flat_state[:, 6:]),
|
| 373 |
+
),
|
| 374 |
+
dim=1,
|
| 375 |
+
)
|
| 376 |
+
).view(batch, steps, 1, HIDDEN_DIM)
|
| 377 |
+
images, cls = self._images(image_primary, image_wrist)
|
| 378 |
+
context = torch.cat((text, state_token, images, cls), dim=2)
|
| 379 |
+
context = context + self.transformer_backbone_position_embedding[:, :steps].to(
|
| 380 |
+
context.dtype
|
| 381 |
+
)
|
| 382 |
+
context = self.embedding_layer_norm(
|
| 383 |
+
context.to(self.embedding_layer_norm.weight.dtype)
|
| 384 |
+
)
|
| 385 |
+
return self.unified_action_world_head(context, sampling_steps=20)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def load_model(
|
| 389 |
+
model_root: str | Path,
|
| 390 |
+
suite: str,
|
| 391 |
+
device: str | torch.device = "cuda",
|
| 392 |
+
*,
|
| 393 |
+
vision_bfloat16: bool = True,
|
| 394 |
+
) -> WorldDiTPolicy:
|
| 395 |
+
"""Load one suite checkpoint from a downloaded model-repository directory."""
|
| 396 |
+
if suite not in SUITES:
|
| 397 |
+
raise ValueError(f"unknown suite {suite!r}; choose one of {SUITES}")
|
| 398 |
+
root = Path(model_root).expanduser().resolve()
|
| 399 |
+
paths = {
|
| 400 |
+
"checkpoint": root / "checkpoints" / suite / "model.safetensors",
|
| 401 |
+
"mae": root / "dependencies" / "mae_pretrain_vit_base.pth",
|
| 402 |
+
"clip": root / "dependencies" / "ViT-B-32.pt",
|
| 403 |
+
}
|
| 404 |
+
missing = [str(path) for path in paths.values() if not path.is_file()]
|
| 405 |
+
if missing:
|
| 406 |
+
raise FileNotFoundError(f"missing model files: {missing}")
|
| 407 |
+
|
| 408 |
+
model = WorldDiTPolicy(paths["mae"], paths["clip"])
|
| 409 |
+
with safe_open(paths["checkpoint"], framework="pt", device="cpu") as source:
|
| 410 |
+
released = {
|
| 411 |
+
name.removeprefix("module."): source.get_tensor(name)
|
| 412 |
+
for name in source.keys()
|
| 413 |
+
}
|
| 414 |
+
current = model.state_dict()
|
| 415 |
+
retained = {name: value for name, value in released.items() if name in current}
|
| 416 |
+
expected = {
|
| 417 |
+
name
|
| 418 |
+
for name in current
|
| 419 |
+
if not name.startswith(("vision_encoder.", "clip_model."))
|
| 420 |
+
}
|
| 421 |
+
missing_parameters = sorted(expected - retained.keys())
|
| 422 |
+
if missing_parameters:
|
| 423 |
+
raise RuntimeError(
|
| 424 |
+
f"checkpoint is missing inference parameters: {missing_parameters}"
|
| 425 |
+
)
|
| 426 |
+
model.load_state_dict(retained, strict=False)
|
| 427 |
+
model.float()
|
| 428 |
+
if vision_bfloat16:
|
| 429 |
+
model.vision_encoder.bfloat16()
|
| 430 |
+
model.to(torch.device(device)).eval()
|
| 431 |
+
return model
|
replay.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2178af4051b31c3faa1d7a590cfd5395ca91ec90f4c2170cd14f4bdcd31050aa
|
| 3 |
+
size 386846
|
requirements.txt
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--extra-index-url https://download.pytorch.org/whl/cu128
|
| 2 |
+
|
| 3 |
+
torch==2.9.1+cu128
|
| 4 |
+
torchvision==0.24.1+cu128
|
| 5 |
+
numpy==1.26.4
|
| 6 |
+
scipy==1.17.1
|
| 7 |
+
pillow==12.3.0
|
| 8 |
+
tqdm==4.68.4
|
| 9 |
+
opencv-python==4.10.0.84
|
| 10 |
+
matplotlib==3.11.1
|
| 11 |
+
omegaconf==2.3.0
|
| 12 |
+
timm==0.9.16
|
| 13 |
+
einops==0.8.2
|
| 14 |
+
einops-exts==0.0.4
|
| 15 |
+
safetensors==0.8.0
|
| 16 |
+
huggingface-hub==0.36.0
|
| 17 |
+
ftfy==6.2.0
|
| 18 |
+
regex==2026.7.10
|
| 19 |
+
packaging==24.0
|
| 20 |
+
git+https://github.com/openai/CLIP.git
|
| 21 |
+
|
| 22 |
+
# Headless LIBERO / robosuite runtime
|
| 23 |
+
numba==0.66.0
|
| 24 |
+
termcolor==3.3.0
|
| 25 |
+
bddl==3.6.0
|
| 26 |
+
mujoco==3.3.2
|
| 27 |
+
gym==0.26.2
|
| 28 |
+
cloudpickle==3.1.2
|
| 29 |
+
easydict==1.13
|
| 30 |
+
glfw==2.10.0
|
| 31 |
+
PyOpenGL==3.1.10
|
| 32 |
+
PyYAML==6.0.3
|