Bidhan Roy commited on
Commit
a4cfa9f
·
0 Parent(s):

Release WorldDiT model and documentation

Browse files
.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