thenewfolder commited on
Commit
f2bec46
·
verified ·
1 Parent(s): 9b00be0

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* 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
 
 
 
 
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
+ examples/franka-coffee.webp filter=lfs diff=lfs merge=lfs -text
37
+ examples/franka-tennis.webp filter=lfs diff=lfs merge=lfs -text
38
+ examples/teaser.webp filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
4
+ .venv/
README.md CHANGED
@@ -1,13 +1,31 @@
1
  ---
2
- title: Dynamic Vla DOM
3
- emoji: 🏢
4
- colorFrom: indigo
5
- colorTo: blue
6
  sdk: gradio
7
  sdk_version: 6.24.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: DynamicVLA DOM Demo
3
+ emoji: 🦾
4
+ colorFrom: green
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 6.24.0
 
8
  app_file: app.py
9
+ short_description: Predict DynamicVLA action chunks on DOM
10
+ python_version: "3.12"
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
+ # DynamicVLA · DOM Demo
15
+
16
+ Unofficial Hugging Face Space for [`hzxie/dynamic-vla-DOM`](https://huggingface.co/hzxie/dynamic-vla-DOM) —
17
+ a 0.4B vision-language-action policy for **dynamic object manipulation**.
18
+
19
+ Upload wrist + opposite-camera frames and a language instruction. The Space
20
+ loads the official checkpoint and returns a **20-step end-effector action chunk**
21
+ (xyz + Euler + gripper) with a 3D path plot.
22
+
23
+ This is a single-chunk policy demo. Closed-loop evaluation still needs the
24
+ official [Isaac Lab stack](https://github.com/hzxie/DynamicVLA).
25
+
26
+ - Paper: [arXiv:2601.22153](https://arxiv.org/abs/2601.22153)
27
+ - Dataset: [`hzxie/DOM`](https://huggingface.co/datasets/hzxie/DOM)
28
+ - Project: [infinitescript.com/project/dynamic-vla](https://www.infinitescript.com/project/dynamic-vla/)
29
+
30
+ Weights are released under NTU S-Lab License 1.0. This Space vendors the
31
+ policy code from the official repo for inference only.
app.py ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """DynamicVLA (hzxie/dynamic-vla-DOM) action-chunk demo."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
14
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
15
+
16
+ import spaces # noqa: E402 — must precede any CUDA-touching import
17
+
18
+ if importlib.util.find_spec("lerobot") is None:
19
+ subprocess.check_call(
20
+ [sys.executable, "-m", "pip", "install", "--no-deps", "lerobot==0.3.3"]
21
+ )
22
+
23
+ import numpy as np # noqa: E402
24
+ import pandas as pd # noqa: E402
25
+ import plotly.graph_objects as go # noqa: E402
26
+ import torch # noqa: E402
27
+ from huggingface_hub import snapshot_download # noqa: E402
28
+ from lerobot.configs.types import FeatureType, PolicyFeature # noqa: E402
29
+ from PIL import Image # noqa: E402
30
+
31
+ from policies.dynamicvla.configuration_dynamicvla import DynamicVLAConfig # noqa: E402
32
+ from policies.dynamicvla.modeling_dynamicvla import ( # noqa: E402
33
+ DynamicVLAPolicy,
34
+ load_dynamicvla,
35
+ )
36
+
37
+ import gradio as gr # noqa: E402
38
+
39
+ MODEL_ID = "hzxie/dynamic-vla-DOM"
40
+ IMG_H, IMG_W = 360, 480
41
+ N_OBS = 2
42
+ ACTION_COLS = ["x", "y", "z", "roll", "pitch", "yaw", "gripper"]
43
+ ROOT = Path(__file__).resolve().parent
44
+ EXAMPLES = ROOT / "examples"
45
+
46
+ FEATURE_TYPES = {
47
+ "STATE": FeatureType.STATE,
48
+ "VISUAL": FeatureType.VISUAL,
49
+ "ACTION": FeatureType.ACTION,
50
+ }
51
+
52
+
53
+ def _features(spec: dict) -> dict:
54
+ return {
55
+ key: PolicyFeature(type=FEATURE_TYPES[ft["type"]], shape=tuple(ft["shape"]))
56
+ for key, ft in spec.items()
57
+ }
58
+
59
+
60
+ def _identity_stats(cfg: DynamicVLAConfig) -> dict[str, dict[str, torch.Tensor]]:
61
+ """MEAN_STD with mean=0 / std=1 is a passthrough. Checkpoint strips norm buffers."""
62
+ stats: dict[str, dict[str, torch.Tensor]] = {}
63
+ for name, feat in {**cfg.input_features, **cfg.output_features}.items():
64
+ if feat.type in (FeatureType.STATE, FeatureType.ACTION):
65
+ shape = tuple(feat.shape)
66
+ stats[name] = {
67
+ "mean": torch.zeros(shape, dtype=torch.float32),
68
+ "std": torch.ones(shape, dtype=torch.float32),
69
+ "min": torch.full(shape, -1.0, dtype=torch.float32),
70
+ "max": torch.ones(shape, dtype=torch.float32),
71
+ }
72
+ return stats
73
+
74
+
75
+ def _build_config(ckpt_dir: str) -> DynamicVLAConfig:
76
+ with open(os.path.join(ckpt_dir, "config.json"), encoding="utf-8") as fh:
77
+ raw = json.load(fh)
78
+ device = "cuda" if torch.cuda.is_available() else "cpu"
79
+ cfg = DynamicVLAConfig(
80
+ input_features=_features(raw["input_features"]),
81
+ output_features=_features(raw["output_features"]),
82
+ device=device,
83
+ )
84
+ skip = {"type", "device", "input_features", "output_features"}
85
+ for key, value in raw.items():
86
+ attr = key.lower()
87
+ if attr in skip or value is None or not hasattr(cfg, attr):
88
+ continue
89
+ setattr(cfg, attr, value)
90
+ cfg.enable_streaming = False
91
+ return cfg
92
+
93
+
94
+ def load_policy() -> DynamicVLAPolicy:
95
+ ckpt_dir = snapshot_download(MODEL_ID)
96
+ cfg = _build_config(ckpt_dir)
97
+ policy = DynamicVLAPolicy(cfg, dataset_stats=_identity_stats(cfg))
98
+ load_dynamicvla(
99
+ policy,
100
+ os.path.join(ckpt_dir, "model.safetensors"),
101
+ device="cpu",
102
+ checkpoint_keys_mapping="model._orig_mod.//model.",
103
+ )
104
+ policy.eval()
105
+ return policy.to("cuda")
106
+
107
+
108
+ POLICY = load_policy()
109
+
110
+
111
+ def _as_image(img) -> Image.Image | None:
112
+ if img is None:
113
+ return None
114
+ if isinstance(img, Image.Image):
115
+ return img.convert("RGB")
116
+ if isinstance(img, np.ndarray):
117
+ if img.ndim == 3 and img.shape[-1] == 4:
118
+ img = img[..., :3]
119
+ if img.dtype != np.uint8:
120
+ img = np.clip(img, 0, 255).astype(np.uint8) if img.max() > 1.5 else (
121
+ np.clip(img * 255.0, 0, 255).astype(np.uint8)
122
+ )
123
+ return Image.fromarray(img).convert("RGB")
124
+ return Image.open(img).convert("RGB")
125
+
126
+
127
+ def _to_nchw(img: Image.Image) -> torch.Tensor:
128
+ resized = img.resize((IMG_W, IMG_H), Image.BILINEAR)
129
+ arr = np.asarray(resized, dtype=np.float32) / 255.0
130
+ return torch.from_numpy(arr).permute(2, 0, 1)
131
+
132
+
133
+ def _stack_obs(current: Image.Image, previous: Image.Image | None) -> torch.Tensor:
134
+ cur = _to_nchw(current)
135
+ prev = _to_nchw(previous) if previous is not None else cur
136
+ return torch.stack([prev, cur], dim=0) # (n_obs, C, H, W)
137
+
138
+
139
+ def _plot_path(actions: np.ndarray) -> go.Figure:
140
+ xs, ys, zs = actions[:, 0], actions[:, 1], actions[:, 2]
141
+ fig = go.Figure(
142
+ data=[
143
+ go.Scatter3d(
144
+ x=xs,
145
+ y=ys,
146
+ z=zs,
147
+ mode="lines+markers",
148
+ marker={"size": 4, "color": np.arange(len(xs)), "colorscale": "Viridis"},
149
+ line={"width": 5, "color": "#2ecc71"},
150
+ name="EE path",
151
+ ),
152
+ go.Scatter3d(
153
+ x=[xs[0]],
154
+ y=[ys[0]],
155
+ z=[zs[0]],
156
+ mode="markers",
157
+ marker={"size": 8, "color": "#27ae60"},
158
+ name="start",
159
+ ),
160
+ go.Scatter3d(
161
+ x=[xs[-1]],
162
+ y=[ys[-1]],
163
+ z=[zs[-1]],
164
+ mode="markers",
165
+ marker={"size": 8, "color": "#e74c3c"},
166
+ name="end",
167
+ ),
168
+ ]
169
+ )
170
+ fig.update_layout(
171
+ template="plotly_dark",
172
+ height=420,
173
+ margin={"l": 0, "r": 0, "t": 30, "b": 0},
174
+ scene={
175
+ "xaxis_title": "x (m)",
176
+ "yaxis_title": "y (m)",
177
+ "zaxis_title": "z (m)",
178
+ "aspectmode": "data",
179
+ },
180
+ title="Predicted 20-step end-effector chunk",
181
+ paper_bgcolor="rgba(0,0,0,0)",
182
+ plot_bgcolor="rgba(0,0,0,0)",
183
+ legend={"orientation": "h"},
184
+ )
185
+ return fig
186
+
187
+
188
+ def _summarize(actions: np.ndarray, elapsed: float, instruction: str) -> str:
189
+ delta = actions[-1, :3] - actions[0, :3]
190
+ grip = actions[:, -1]
191
+ return (
192
+ f"**Instruction:** {instruction.strip()}\n\n"
193
+ f"**Chunk:** {len(actions)} steps · **{elapsed:.2f}s** GPU\n\n"
194
+ f"- Start xyz: `{actions[0, :3].round(4).tolist()}`\n"
195
+ f"- End xyz: `{actions[-1, :3].round(4).tolist()}`\n"
196
+ f"- Net Δxyz: `{delta.round(4).tolist()}`\n"
197
+ f"- Gripper: min `{grip.min():.3f}` → max `{grip.max():.3f}` "
198
+ f"(last `{grip[-1]:.3f}`)\n"
199
+ f"- Rotation (last rpy): `{actions[-1, 3:6].round(4).tolist()}`"
200
+ )
201
+
202
+
203
+ @spaces.GPU(duration=60)
204
+ def predict_action_chunk(
205
+ wrist: np.ndarray | Image.Image | None,
206
+ opposite: np.ndarray | Image.Image | None,
207
+ wrist_prev: np.ndarray | Image.Image | None,
208
+ opposite_prev: np.ndarray | Image.Image | None,
209
+ instruction: str,
210
+ x: float,
211
+ y: float,
212
+ z: float,
213
+ roll: float,
214
+ pitch: float,
215
+ yaw: float,
216
+ apply_delta: bool,
217
+ ) -> tuple[pd.DataFrame, go.Figure, str]:
218
+ """Predict a 20-step DynamicVLA action chunk from dual-camera frames."""
219
+ if not instruction or not instruction.strip():
220
+ raise gr.Error("Provide a language instruction.")
221
+ wrist_img = _as_image(wrist)
222
+ opp_img = _as_image(opposite)
223
+ if wrist_img is None or opp_img is None:
224
+ raise gr.Error("Upload both wrist and opposite camera frames.")
225
+
226
+ device = "cuda"
227
+ wrist_t = _stack_obs(wrist_img, _as_image(wrist_prev)).unsqueeze(0).to(device)
228
+ opp_t = _stack_obs(opp_img, _as_image(opposite_prev)).unsqueeze(0).to(device)
229
+ state = torch.tensor(
230
+ [[[x, y, z, roll, pitch, yaw]] * N_OBS], dtype=torch.float32, device=device
231
+ )
232
+
233
+ batch = {
234
+ "observation.images.wrist_cam": wrist_t,
235
+ "observation.images.opst_cam": opp_t,
236
+ "observation.state": state,
237
+ "task": [instruction.strip()],
238
+ }
239
+
240
+ POLICY.reset()
241
+ tick = time.perf_counter()
242
+ with torch.inference_mode():
243
+ actions = POLICY.predict_action_chunk(batch)
244
+ if apply_delta and getattr(POLICY.config, "use_delta_action", True):
245
+ actions = actions.clone()
246
+ actions[..., :6] = actions[..., :6] + state[:, -1:, :6]
247
+ elapsed = time.perf_counter() - tick
248
+ acts = actions[0].detach().float().cpu().numpy()
249
+
250
+ table = pd.DataFrame(acts, columns=ACTION_COLS)
251
+ table.insert(0, "step", np.arange(len(table)))
252
+ return table, _plot_path(acts), _summarize(acts, elapsed, instruction)
253
+
254
+
255
+ def _example_row(stem: str, instruction: str) -> list:
256
+ img = str(EXAMPLES / f"{stem}.png")
257
+ return [img, img, None, None, instruction, 0.40, 0.00, 0.30, 0.0, 0.0, 0.0, True]
258
+
259
+
260
+ GALLERY_MD = """
261
+ ## DynamicVLA on DOM
262
+
263
+ **DynamicVLA** (0.4B, SmolLM2-360M + FastViT) is a VLA for *moving* objects.
264
+ It adds **Continuous Inference** and **Latent-aware Action Streaming** so the
265
+ policy does not freeze between action chunks.
266
+
267
+ This Space runs the official [`hzxie/dynamic-vla-DOM`](https://huggingface.co/hzxie/dynamic-vla-DOM)
268
+ checkpoint and predicts a **20-step** 7-DoF end-effector chunk
269
+ (`[x, y, z, roll, pitch, yaw, gripper]`).
270
+
271
+ Closed-loop Isaac Lab eval is *not* hosted here — use
272
+ [hzxie/DynamicVLA](https://github.com/hzxie/DynamicVLA) for that.
273
+
274
+ | | |
275
+ |---|---|
276
+ | Paper | [arXiv:2601.22153](https://arxiv.org/abs/2601.22153) |
277
+ | Dataset | [`hzxie/DOM`](https://huggingface.co/datasets/hzxie/DOM) — 200K episodes, 2.8K scenes, 206 objects |
278
+ | Weights | [`hzxie/dynamic-vla-DOM`](https://huggingface.co/hzxie/dynamic-vla-DOM) |
279
+ | Project | [infinitescript.com/project/dynamic-vla](https://www.infinitescript.com/project/dynamic-vla/) |
280
+ | Spotlight | [YouTube](https://youtu.be/NmJnHcI04_Q) |
281
+
282
+ <iframe width="100%" height="360" src="https://www.youtube.com/embed/NmJnHcI04_Q"
283
+ title="DynamicVLA spotlight" frameborder="0"
284
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
285
+ allowfullscreen></iframe>
286
+ """
287
+
288
+
289
+ def build_ui() -> gr.Blocks:
290
+ theme = gr.themes.Soft(primary_hue="green", neutral_hue="zinc").set(
291
+ body_background_fill="#0b0f0c",
292
+ block_background_fill="#121814",
293
+ )
294
+ with gr.Blocks(theme=theme, title="DynamicVLA · DOM") as demo:
295
+ gr.Markdown(
296
+ "# DynamicVLA — DOM action-chunk demo\n"
297
+ "0.4B VLA for dynamic object manipulation. "
298
+ "Upload wrist + scene cameras, write an instruction, get a 20-step EE chunk."
299
+ )
300
+ with gr.Tabs():
301
+ with gr.Tab("Predict"):
302
+ with gr.Row():
303
+ with gr.Column():
304
+ wrist = gr.Image(label="Wrist camera (current)", type="numpy")
305
+ opposite = gr.Image(
306
+ label="Opposite / scene camera (current)", type="numpy"
307
+ )
308
+ with gr.Accordion("Previous frames (optional, n_obs=2)", open=False):
309
+ wrist_prev = gr.Image(
310
+ label="Wrist camera (t−1)", type="numpy"
311
+ )
312
+ opposite_prev = gr.Image(
313
+ label="Opposite camera (t−1)", type="numpy"
314
+ )
315
+ instruction = gr.Textbox(
316
+ label="Language instruction",
317
+ placeholder="Pick up the rolling cylinder and place it onto the wooden block.",
318
+ lines=2,
319
+ )
320
+ with gr.Accordion("Current EE state (meters / rad)", open=False):
321
+ with gr.Row():
322
+ x = gr.Number(value=0.40, label="x")
323
+ y = gr.Number(value=0.00, label="y")
324
+ z = gr.Number(value=0.30, label="z")
325
+ with gr.Row():
326
+ roll = gr.Number(value=0.0, label="roll")
327
+ pitch = gr.Number(value=0.0, label="pitch")
328
+ yaw = gr.Number(value=0.0, label="yaw")
329
+ apply_delta = gr.Checkbox(
330
+ value=True,
331
+ label="Add delta actions to current EE state",
332
+ )
333
+ run = gr.Button("Predict action chunk", variant="primary")
334
+ with gr.Column():
335
+ summary = gr.Markdown("Upload both views and run.")
336
+ path = gr.Plot(label="EE trajectory")
337
+ table = gr.Dataframe(label="Action chunk (20 × 7)")
338
+
339
+ inputs = [
340
+ wrist,
341
+ opposite,
342
+ wrist_prev,
343
+ opposite_prev,
344
+ instruction,
345
+ x,
346
+ y,
347
+ z,
348
+ roll,
349
+ pitch,
350
+ yaw,
351
+ apply_delta,
352
+ ]
353
+ run.click(
354
+ fn=predict_action_chunk,
355
+ inputs=inputs,
356
+ outputs=[table, path, summary],
357
+ )
358
+ gr.Examples(
359
+ examples=[
360
+ _example_row(
361
+ "franka-coffee",
362
+ "Pick up the rolling cylinder and place it onto the wooden block.",
363
+ ),
364
+ _example_row(
365
+ "piper-sesame",
366
+ "Grasp the rolling roasted sesame container and place it onto the blue frisbee.",
367
+ ),
368
+ _example_row(
369
+ "franka-tennis",
370
+ "Get hold of the moving tennis ball and position it into the paper bowl.",
371
+ ),
372
+ ],
373
+ inputs=inputs,
374
+ label="DOM-style prompts (official comparison stills as both views)",
375
+ cache_examples=True,
376
+ cache_mode="lazy",
377
+ )
378
+
379
+ with gr.Tab("About"):
380
+ gr.Markdown(GALLERY_MD)
381
+ if (EXAMPLES / "teaser.webp").exists():
382
+ gr.Image(
383
+ value=str(EXAMPLES / "teaser.webp"),
384
+ label="Official teaser",
385
+ interactive=False,
386
+ )
387
+ gr.Markdown(
388
+ "```bibtex\n"
389
+ "@article{xie2026dynamicvla,\n"
390
+ " title = {DynamicVLA: A Vision-Language-Action Model for Dynamic Object Manipulation},\n"
391
+ " author = {Xie, Haozhe and Wen, Beichen and Zheng, Jiarui and Chen, Zhaoxi\n"
392
+ " and Hong, Fangzhou and Diao, Haiwen and Liu, Ziwei},\n"
393
+ " journal = {arXiv preprint arXiv:2601.22153},\n"
394
+ " year = {2026}\n"
395
+ "}\n"
396
+ "```"
397
+ )
398
+ return demo
399
+
400
+
401
+ demo = build_ui()
402
+
403
+ if __name__ == "__main__":
404
+ demo.launch(mcp_server=True)
examples/franka-coffee.png ADDED
examples/franka-coffee.webp ADDED

Git LFS Details

  • SHA256: aca30557288584edb4083f5d2ae067c16feabf05132f398ac209ee6f3372d454
  • Pointer size: 131 Bytes
  • Size of remote file: 167 kB
examples/franka-tennis.png ADDED
examples/franka-tennis.webp ADDED

Git LFS Details

  • SHA256: 11510831ba6c180ded372d96b509c1c37bf62858c3e904a727f90347a3f3265c
  • Pointer size: 131 Bytes
  • Size of remote file: 164 kB
examples/piper-sesame.png ADDED
examples/piper-sesame.webp ADDED
examples/teaser.webp ADDED

Git LFS Details

  • SHA256: 6885c3e1e25c331a4f61bbeaadaf0b3114236eff5d9df65847879e39d6c8cf37
  • Pointer size: 131 Bytes
  • Size of remote file: 169 kB
policies/__init__.py ADDED
File without changes
policies/dynamicvla/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Vendored DynamicVLA policy package from https://github.com/hzxie/DynamicVLA."""
policies/dynamicvla/configuration_dynamicvla.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # @File: configuration_dynamicvla.py
4
+ # @Author: Haozhe Xie
5
+ # @Date: 2025-08-21 15:22:31
6
+ # @Last Modified by: Haozhe Xie
7
+ # @Last Modified at: 2026-02-26 10:04:17
8
+ # @Email: root@haozhexie.com
9
+
10
+ from dataclasses import dataclass, field
11
+
12
+ from lerobot.configs.policies import PreTrainedConfig
13
+ from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature
14
+ from lerobot.optim.optimizers import AdamWConfig
15
+ from lerobot.optim.schedulers import CosineDecayWithWarmupSchedulerConfig
16
+
17
+
18
+ @PreTrainedConfig.register_subclass("dynamicvla")
19
+ @dataclass
20
+ class DynamicVLAConfig(PreTrainedConfig):
21
+ # Input / output structure.
22
+ n_obs_steps: int = 1
23
+ chunk_size: int = 50
24
+ n_action_steps: int = 50
25
+
26
+ normalization_mapping: dict[str, NormalizationMode] = field(
27
+ default_factory=lambda: {
28
+ "VISUAL": NormalizationMode.IDENTITY,
29
+ "STATE": NormalizationMode.MEAN_STD,
30
+ "ACTION": NormalizationMode.MEAN_STD,
31
+ }
32
+ )
33
+
34
+ # Shorter state and action vectors will be padded
35
+ max_state_dim: int = 32
36
+ max_action_dim: int = 32
37
+ # Image preprocessing
38
+ resize_imgs_with_padding: tuple[int, int] = (512, 512)
39
+ # Add empty images. Used by dynamicvla_aloha_sim which adds the empty
40
+ # left and right wrist cameras in addition to the top camera.
41
+ empty_cameras: int = 0
42
+ # Converts the joint and gripper values from the standard Aloha space to the space
43
+ # used by the pi internal runtime which was used to train the base model.
44
+ adapt_to_pi_aloha: bool = False
45
+ # Converts joint dimensions to deltas with respect to the current state before
46
+ # passing to the model. Gripper dimensions will remain in absolute values.
47
+ use_delta_joint_actions_aloha: bool = False
48
+ # Use delta action prediction (relative to the current robot state)
49
+ use_delta_action: bool = True
50
+ # Streaming inference
51
+ enable_streaming: bool = False
52
+ # Multi-timestep fusion ("conv" or "attn")
53
+ temporal_fusion: str = "conv"
54
+ # Tokenizer
55
+ tokenizer_max_length: int = 48
56
+ # Decoding
57
+ num_steps: int = 10
58
+ # Attention utils
59
+ use_cache: bool = True
60
+ # Finetuning settings
61
+ freeze_vision_model: bool = True
62
+ freeze_connector: bool = True
63
+ freeze_text_model: bool = True
64
+ train_state_proj: bool = True
65
+ # Training presets
66
+ optimizer_lr: float = 1e-4
67
+ optimizer_betas: tuple[float, float] = (0.9, 0.95)
68
+ optimizer_eps: float = 1e-8
69
+ optimizer_weight_decay: float = 1e-10
70
+ optimizer_grad_clip_norm: float = 10
71
+ scheduler_warmup_steps: int = 1_000
72
+ scheduler_decay_steps: int = 30_000
73
+ scheduler_decay_lr: float = 2.5e-6
74
+ # Select the VLM backbone.
75
+ attention_mode: str = "cross_attn"
76
+ prefix_length: int = -1
77
+ pad_language_to: str = "longest" # "max_length"
78
+ # Less or equal to 0 is the default where the action expert has the same number of
79
+ # layers of VLM. Otherwise the expert have less layers.
80
+ num_expert_layers: int = -1
81
+ num_expert_skip_layers: int = 0
82
+ # VLM settings
83
+ vlm_model_name: str = "HuggingFaceTB/SmolLM2-360M-Instruct"
84
+ num_vlm_layers: int = 16
85
+ # SmolVLM Settings
86
+ smolvlm_patch_size: int = 16
87
+ smolvlm_attention_heads: int = 12
88
+ smolvlm_hidden_size: int = 768
89
+ smolvlm_intermediate_size: int = 3072
90
+ # FastVLM Settings
91
+ fastvlm_inference_mode: bool = True
92
+ # Interleave SA layers each self_attn_every_n_layers
93
+ self_attn_every_n_layers: int = 2
94
+ # The action expert hidden size (wrt to the VLM)
95
+ expert_width_multiplier: float = 0.75
96
+ # sensitivity range for the timestep used in sine-cosine positional encoding
97
+ min_period: float = 4e-3
98
+ max_period: float = 4.0
99
+
100
+ def __post_init__(self):
101
+ super().__post_init__()
102
+
103
+ """Input validation (not exhaustive)."""
104
+ if self.n_action_steps > self.chunk_size:
105
+ raise ValueError(
106
+ "The chunk size is the upper bound for the number of action steps per"
107
+ f" model invocation. Got {self.n_action_steps} for `n_action_steps` and"
108
+ f" {self.chunk_size} for `chunk_size`."
109
+ )
110
+ if self.use_delta_joint_actions_aloha:
111
+ raise NotImplementedError(
112
+ "`use_delta_joint_actions_aloha` is used by dynamicvla for aloha real"
113
+ " models. It is not ported yet in LeRobot."
114
+ )
115
+
116
+ def validate_features(self) -> None:
117
+ for i in range(self.empty_cameras):
118
+ key = f"observation.images.empty_camera_{i}"
119
+ empty_camera = PolicyFeature(
120
+ type=FeatureType.VISUAL,
121
+ shape=(3, 480, 640),
122
+ )
123
+ self.input_features[key] = empty_camera
124
+
125
+ def get_optimizer_preset(self) -> AdamWConfig:
126
+ return AdamWConfig(
127
+ lr=self.optimizer_lr,
128
+ betas=self.optimizer_betas,
129
+ eps=self.optimizer_eps,
130
+ weight_decay=self.optimizer_weight_decay,
131
+ grad_clip_norm=self.optimizer_grad_clip_norm,
132
+ )
133
+
134
+ def get_scheduler_preset(self):
135
+ return CosineDecayWithWarmupSchedulerConfig(
136
+ peak_lr=self.optimizer_lr,
137
+ decay_lr=self.scheduler_decay_lr,
138
+ num_warmup_steps=self.scheduler_warmup_steps,
139
+ num_decay_steps=self.scheduler_decay_steps,
140
+ )
141
+
142
+ @property
143
+ def observation_delta_indices(self) -> list:
144
+ return [0]
145
+
146
+ @property
147
+ def action_delta_indices(self) -> list:
148
+ return list(range(self.chunk_size))
149
+
150
+ @property
151
+ def reward_delta_indices(self) -> None:
152
+ return None
policies/dynamicvla/modeling_dynamicvla.py ADDED
@@ -0,0 +1,1189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # @File: modeling_dynamicvla.py
4
+ # @Author: Haozhe Xie
5
+ # @Date: 2025-08-21 15:23:45
6
+ # @Last Modified by: Haozhe Xie
7
+ # @Last Modified at: 2026-02-26 10:10:57
8
+ # @Email: root@haozhexie.com
9
+
10
+ import logging
11
+ import math
12
+ import os
13
+ import re
14
+ import time
15
+ from collections import deque
16
+
17
+ import safetensors
18
+ import torch
19
+ import torch.multiprocessing as mp
20
+ import torch.nn.functional as F # noqa: N812
21
+ from lerobot.constants import ACTION, OBS_STATE
22
+ from lerobot.policies.normalize import Normalize, Unnormalize
23
+ from lerobot.policies.pretrained import PreTrainedPolicy
24
+ from lerobot.policies.utils import populate_queues
25
+ from lerobot.utils.utils import get_safe_dtype
26
+ from transformers import AutoConfig, SmolVLMForConditionalGeneration
27
+
28
+ from policies.dynamicvla.configuration_dynamicvla import DynamicVLAConfig
29
+ from policies.dynamicvla.modeling_fastvlm import (
30
+ FastViTConfig,
31
+ FastVLMConfig,
32
+ FastVLMForConditionalGeneration,
33
+ )
34
+ from policies.dynamicvla.modeling_vlm_with_expert import VLMWithExpertModel
35
+
36
+ # Matches ".soNNN", optionally followed by "-something", up to the "_buffer_" marker
37
+ _VARIANT_RE = re.compile(r"\.so\d+(?:-[\w]+)?_buffer_")
38
+
39
+
40
+ def canonicalise(k: str) -> str:
41
+ """
42
+ Remove dataset-variant markers like '.so100-blue_' or '.so100_' from a
43
+ normalisation-buffer key.
44
+ """
45
+ return _VARIANT_RE.sub(".buffer_", k)
46
+
47
+
48
+ def standardise_state_dict(
49
+ checkpoint: dict[str, torch.Tensor], ref_keys: set[str], *, verbose: bool = True
50
+ ) -> tuple[dict[str, torch.Tensor], list[str]]:
51
+ """
52
+ • Re-keys `checkpoint ` so that every entry matches the *reference* key set.
53
+ • If several variant keys collapse to the same canonical name we keep the
54
+ first one and log the collision.
55
+ • Returns the new dict + a list of entries that could not be matched.
56
+ """
57
+ out, collisions, unmatched = {}, {}, []
58
+
59
+ for k, v in checkpoint.items():
60
+ canon = canonicalise(k)
61
+ if canon in ref_keys:
62
+ if canon in out: # duplicate after collapsing
63
+ collisions.setdefault(canon, []).append(k)
64
+ else:
65
+ out[canon] = v
66
+ else:
67
+ unmatched.append(k)
68
+
69
+ if verbose:
70
+ for canon, variants in collisions.items():
71
+ logging.info(f"[standardise_state_dict] '{canon}' ← {variants}")
72
+ if unmatched:
73
+ logging.info(
74
+ f"[standardise_state_dict] kept {len(unmatched)} unmatched keys"
75
+ )
76
+
77
+ out.update({k: checkpoint[k] for k in unmatched})
78
+ return out, unmatched
79
+
80
+
81
+ def rename_checkpoint_keys(checkpoint: dict, rename_str: str):
82
+ """
83
+ Renames keys in a checkpoint dictionary based on the given rename string.
84
+
85
+ Args:
86
+ checkpoint (dict): The checkpoint dictionary.
87
+ rename_str (str): A string specifying key mappings in the format "old1//new1,old2//new2".
88
+
89
+ Returns:
90
+ dict: The modified checkpoint with renamed keys.
91
+ """
92
+
93
+ rename_dict = dict(pair.split("//") for pair in rename_str.split(","))
94
+
95
+ new_checkpoint = {}
96
+ for k, v in checkpoint.items():
97
+ for old_key, new_key in rename_dict.items():
98
+ if old_key in k:
99
+ k = k.replace(old_key, new_key)
100
+ new_checkpoint[k] = v
101
+ return new_checkpoint
102
+
103
+
104
+ def load_dynamicvla(
105
+ model: torch.nn.Module,
106
+ filename: str | os.PathLike,
107
+ *,
108
+ device: str = "cpu",
109
+ checkpoint_keys_mapping: str = "",
110
+ ) -> torch.nn.Module:
111
+ state_dict = safetensors.torch.load_file(filename, device=device)
112
+
113
+ # Optional user-supplied renames (e.g. "model._orig_mod.//model.")
114
+ if checkpoint_keys_mapping and "//" in checkpoint_keys_mapping:
115
+ state_dict = rename_checkpoint_keys(state_dict, checkpoint_keys_mapping)
116
+
117
+ state_dict, _ = standardise_state_dict(state_dict, set(model.state_dict().keys()))
118
+
119
+ # HACK(aliberts): to not overwrite normalization parameters as they should come from the dataset
120
+ norm_keys = ("normalize_inputs", "normalize_targets", "unnormalize_outputs")
121
+ state_dict = {k: v for k, v in state_dict.items() if not k.startswith(norm_keys)}
122
+
123
+ missing, unexpected = model.load_state_dict(state_dict, strict=False)
124
+ if not all(key.startswith(norm_keys) for key in missing) or unexpected:
125
+ raise RuntimeError(
126
+ "DynamicVLA %d missing / %d unexpected keys"
127
+ % (len(missing), len(unexpected))
128
+ )
129
+
130
+ return model
131
+
132
+
133
+ def create_sinusoidal_pos_embedding(
134
+ time: torch.tensor,
135
+ dimension: int,
136
+ min_period: float,
137
+ max_period: float,
138
+ device="cpu",
139
+ ) -> torch.Tensor:
140
+ """Computes sine-cosine positional embedding vectors for scalar positions."""
141
+ if dimension % 2 != 0:
142
+ raise ValueError(f"dimension ({dimension}) must be divisible by 2")
143
+
144
+ if time.ndim != 1:
145
+ raise ValueError(
146
+ "The time torch.Tensor is expected to be of shape `(batch_size, )`."
147
+ )
148
+
149
+ dtype = get_safe_dtype(torch.float64, device.type)
150
+ fraction = torch.linspace(0.0, 1.0, dimension // 2, dtype=dtype, device=device)
151
+ period = min_period * (max_period / min_period) ** fraction
152
+
153
+ # Compute the outer product
154
+ scaling_factor = 1.0 / period * 2 * math.pi
155
+ sin_input = scaling_factor[None, :] * time[:, None]
156
+ pos_emb = torch.cat([torch.sin(sin_input), torch.cos(sin_input)], dim=1)
157
+ return pos_emb
158
+
159
+
160
+ def make_att_2d_masks(pad_masks, att_masks):
161
+ """Copied from big_vision.
162
+
163
+ Tokens can attend to valid inputs tokens which have a cumulative mask_ar
164
+ smaller or equal to theirs. This way `mask_ar` int[B, N] can be used to
165
+ setup several types of attention, for example:
166
+
167
+ [[1 1 1 1 1 1]]: pure causal attention.
168
+
169
+ [[0 0 0 1 1 1]]: prefix-lm attention. The first 3 tokens can attend between
170
+ themselves and the last 3 tokens have a causal attention. The first
171
+ entry could also be a 1 without changing behaviour.
172
+
173
+ [[1 0 1 0 1 0 0 1 0 0]]: causal attention between 4 blocks. Tokens of a
174
+ block can attend all previous blocks and all tokens on the same block.
175
+
176
+ Args:
177
+ input_mask: bool[B, N] true if its part of the input, false if padding.
178
+ mask_ar: int32[B, N] mask that's 1 where previous tokens cannot depend on
179
+ it and 0 where it shares the same attention mask as the previous token.
180
+ """
181
+ if att_masks.ndim != 2:
182
+ raise ValueError(att_masks.ndim)
183
+ if pad_masks.ndim != 2:
184
+ raise ValueError(pad_masks.ndim)
185
+
186
+ cumsum = torch.cumsum(att_masks, dim=1)
187
+ att_2d_masks = cumsum[:, None, :] <= cumsum[:, :, None]
188
+ pad_2d_masks = pad_masks[:, None, :] * pad_masks[:, :, None]
189
+ att_2d_masks = att_2d_masks & pad_2d_masks
190
+ return att_2d_masks
191
+
192
+
193
+ def resize_with_pad(img, width, height, pad_value=-1):
194
+ # assume no-op when width height fits already
195
+ if img.ndim != 4:
196
+ raise ValueError(f"(b,c,h,w) expected, but {img.shape}")
197
+
198
+ cur_height, cur_width = img.shape[2:]
199
+
200
+ ratio = max(cur_width / width, cur_height / height)
201
+ resized_height = int(cur_height / ratio)
202
+ resized_width = int(cur_width / ratio)
203
+ resized_img = F.interpolate(
204
+ img, size=(resized_height, resized_width), mode="bilinear", align_corners=False
205
+ )
206
+
207
+ pad_height = max(0, int(height - resized_height))
208
+ pad_width = max(0, int(width - resized_width))
209
+
210
+ # pad on left and top of image
211
+ padded_img = F.pad(resized_img, (pad_width, 0, pad_height, 0), value=pad_value)
212
+ return padded_img
213
+
214
+
215
+ def pad_vector(vector, new_dim):
216
+ """Can be (batch_size x sequence_length x features_dimension)
217
+ or (batch_size x features_dimension)
218
+ """
219
+ if vector.shape[-1] == new_dim:
220
+ return vector
221
+ shape = list(vector.shape)
222
+ current_dim = shape[-1]
223
+ shape[-1] = new_dim
224
+ new_vector = torch.zeros(*shape, dtype=vector.dtype, device=vector.device)
225
+ new_vector[..., :current_dim] = vector
226
+ return new_vector
227
+
228
+
229
+ def normalize(x, min_val, max_val):
230
+ return (x - min_val) / (max_val - min_val)
231
+
232
+
233
+ def unnormalize(x, min_val, max_val):
234
+ return x * (max_val - min_val) + min_val
235
+
236
+
237
+ def safe_arcsin(value):
238
+ # This ensures that the input stays within
239
+ # [−1,1] to avoid invalid values for arcsin
240
+ return torch.arcsin(torch.clamp(value, -1.0, 1.0))
241
+
242
+
243
+ def aloha_gripper_to_angular(value):
244
+ # Aloha transforms the gripper positions into a linear space. The following code
245
+ # reverses this transformation to be consistent with dynamicvla which is pretrained in
246
+ # angular space.
247
+ #
248
+ # These values are coming from the Aloha code:
249
+ # PUPPET_GRIPPER_POSITION_OPEN, PUPPET_GRIPPER_POSITION_CLOSED
250
+ value = unnormalize(value, min_val=0.01844, max_val=0.05800)
251
+
252
+ # This is the inverse of the angular to linear transformation inside the Interbotix code.
253
+ def linear_to_radian(linear_position, arm_length, horn_radius):
254
+ value = (horn_radius**2 + linear_position**2 - arm_length**2) / (
255
+ 2 * horn_radius * linear_position
256
+ )
257
+ return safe_arcsin(value)
258
+
259
+ # The constants are taken from the Interbotix code.
260
+ value = linear_to_radian(value, arm_length=0.036, horn_radius=0.022)
261
+
262
+ # Normalize to [0, 1].
263
+ # The values 0.4 and 1.5 were measured on an actual Trossen robot.
264
+ return normalize(value, min_val=0.4, max_val=1.5)
265
+
266
+
267
+ def aloha_gripper_from_angular(value):
268
+ # Convert from the gripper position used by dynamicvla to the gripper position that is used by Aloha.
269
+ # Note that the units are still angular but the range is different.
270
+
271
+ # The values 0.4 and 1.5 were measured on an actual Trossen robot.
272
+ value = unnormalize(value, min_val=0.4, max_val=1.5)
273
+
274
+ # These values are coming from the Aloha code:
275
+ # PUPPET_GRIPPER_JOINT_OPEN, PUPPET_GRIPPER_JOINT_CLOSE
276
+ return normalize(value, min_val=-0.6213, max_val=1.4910)
277
+
278
+
279
+ def aloha_gripper_from_angular_inv(value):
280
+ # Directly inverts the gripper_from_angular function.
281
+ value = unnormalize(value, min_val=-0.6213, max_val=1.4910)
282
+ return normalize(value, min_val=0.4, max_val=1.5)
283
+
284
+
285
+ class DynamicVLAPolicy(PreTrainedPolicy):
286
+ """Wrapper class around VLAFlowMatching model to train and run inference within LeRobot."""
287
+
288
+ config_class = DynamicVLAConfig
289
+ name = "dynamicvla"
290
+
291
+ def __init__(
292
+ self,
293
+ config: DynamicVLAConfig,
294
+ dataset_stats: dict[str, dict[str, torch.Tensor]] | None = None,
295
+ ckpt_filename: str | None = None,
296
+ ):
297
+ """
298
+ Args:
299
+ config: Policy configuration class instance or None, in which case the default instantiation of
300
+ the configuration class is used.
301
+ dataset_stats: Dataset statistics to be used for normalization. If not passed here, it is expected
302
+ that they will be passed with a call to `load_state_dict` before the policy is used.
303
+ ckpt_filename: The file path of the pretrained model checkpoint. This is only used when
304
+ `config.enable_streaming` is set to True, in which case a separate process
305
+ is spawned to run the VLA model for streaming inference.
306
+ """
307
+
308
+ super().__init__(config)
309
+ config.validate_features()
310
+ self.config = config
311
+ self.normalize_inputs = Normalize(
312
+ config.input_features, config.normalization_mapping, dataset_stats
313
+ )
314
+ self.normalize_targets = Normalize(
315
+ config.output_features, config.normalization_mapping, dataset_stats
316
+ )
317
+ self.unnormalize_outputs = Unnormalize(
318
+ config.output_features, config.normalization_mapping, dataset_stats
319
+ )
320
+ self.model = VLAFlowMatching(config)
321
+ self.language_tokenizer = self.model.vlm_with_expert.tokenizer
322
+ self.reset()
323
+
324
+ # ckpt_filename is used to initlialize the streaming process.
325
+ # The variable is only set once in the get_streaming_model function.
326
+ if config.enable_streaming and ckpt_filename is not None:
327
+ # The initialization of the wrapper process of _inference_loop
328
+ ctx = mp.get_context("spawn")
329
+ self.q_in = ctx.Manager().dict()
330
+ self.q_out = ctx.Queue(maxsize=1)
331
+ self.worker = ctx.Process(
332
+ target=self._inference_loop,
333
+ args=(ckpt_filename, config, self.q_in, self.q_out),
334
+ )
335
+ self.worker.daemon = True
336
+ self.worker.start()
337
+ # Wait for the VLA model to be initialized
338
+ _ = self.q_out.get()
339
+ assert "initialized" in _
340
+
341
+ def reset(self):
342
+ """This should be called whenever the environment is reset."""
343
+ self._queues = {ACTION: deque(maxlen=self.config.n_action_steps)}
344
+ self._obs_index = 0
345
+ self._action_index = 0
346
+ if hasattr(self, "q_in"):
347
+ self.q_in.clear() # Clear the input queue
348
+ if hasattr(self, "q_out") and not self.q_out.empty():
349
+ self.q_out.get_nowait() # Clear the output queue
350
+
351
+ @staticmethod
352
+ def get_streaming_model(pretrained_model: str, vla_cfg: DynamicVLAConfig):
353
+ wrapper = DynamicVLAPolicy.from_pretrained(
354
+ pretrained_model, config=vla_cfg, ckpt_filename=pretrained_model
355
+ )
356
+ # Remove unnecessary components to reduce VRAM
357
+ del wrapper.language_tokenizer, wrapper.model
358
+ torch.cuda.empty_cache()
359
+
360
+ return wrapper
361
+
362
+ @staticmethod
363
+ @torch.no_grad()
364
+ def _inference_loop(
365
+ pretrained_model: str, vla_cfg: DynamicVLAConfig, q_in: dict, q_out: mp.Queue
366
+ ):
367
+ logging.basicConfig(
368
+ level=logging.INFO, format="[%(levelname)s] %(asctime)s %(message)s"
369
+ )
370
+ # Initialization
371
+ vla_model = DynamicVLAPolicy.from_pretrained(pretrained_model, config=vla_cfg)
372
+ vla_model.eval()
373
+ if torch.cuda.is_available():
374
+ vla_model = vla_model.cuda()
375
+ # Warm-up (to accelerate the first inference)
376
+ dummy_batch = {
377
+ k: torch.zeros(
378
+ 1,
379
+ vla_cfg.n_obs_steps,
380
+ *vla_cfg.input_features[k].shape,
381
+ dtype=torch.float32,
382
+ device="cuda" if torch.cuda.is_available() else "cpu",
383
+ )
384
+ for k in vla_cfg.input_features
385
+ }
386
+ dummy_batch["task"] = ["dummy text input"]
387
+ vla_model._get_action_chunk(dummy_batch)
388
+ q_out.put({"initialized": True})
389
+
390
+ # The streaming inference loop
391
+ while True:
392
+ try:
393
+ latest_obs = q_in.get("obs")
394
+ except:
395
+ latest_obs = None
396
+
397
+ if latest_obs is None:
398
+ continue
399
+
400
+ q_in.clear()
401
+ noise = latest_obs[1].cuda() if latest_obs[1] is not None else None
402
+ batch = {}
403
+ for k, v in latest_obs[0].items():
404
+ if isinstance(v, torch.Tensor):
405
+ batch[k] = v.cuda() if torch.cuda.is_available() else v
406
+ else:
407
+ batch[k] = v
408
+
409
+ index = batch["index"]
410
+ latest_state = batch[OBS_STATE][:, -1:, :]
411
+ batch = vla_model._prepare_batch(batch)
412
+
413
+ actions = vla_model._get_action_chunk(batch, noise)
414
+ if vla_model.config.use_delta_action:
415
+ action_dim = actions.shape[-1] - 1
416
+ actions[..., :action_dim] += latest_state[..., :action_dim]
417
+
418
+ if q_out.full():
419
+ logging.warning("The output queue is full. Skipping an action.")
420
+ continue
421
+
422
+ # NOTE: All torch.Tensors are on CPU if streaming is enabled. Because IPC with
423
+ # CUDA torch.Tensors is not supported.
424
+ q_out.put_nowait({"actions": actions.transpose(0, 1).cpu(), "index": index})
425
+
426
+ @classmethod
427
+ def _load_as_safetensor(
428
+ cls,
429
+ model: "DynamicVLAPolicy",
430
+ model_file: str,
431
+ map_location: str,
432
+ strict: bool,
433
+ ):
434
+ safetensors.torch.load_model(
435
+ model, model_file, strict=strict, device=map_location
436
+ )
437
+ return load_dynamicvla(
438
+ model,
439
+ model_file,
440
+ device=map_location,
441
+ checkpoint_keys_mapping="model._orig_mod.//model.",
442
+ )
443
+
444
+ def get_optim_params(self) -> dict:
445
+ return self.parameters()
446
+
447
+ def _get_action_chunk(
448
+ self, batch: dict[str, torch.Tensor], noise: torch.Tensor | None = None
449
+ ) -> torch.Tensor:
450
+ tick = time.perf_counter()
451
+ for k in batch:
452
+ if k in self._queues and k != ACTION:
453
+ batch[k] = torch.stack(list(self._queues[k]), dim=1)
454
+
455
+ images, img_masks = self.prepare_images(batch)
456
+ state = self.prepare_state(batch)
457
+ lang_tokens, lang_masks = self.prepare_language(batch)
458
+
459
+ actions = self.model.sample_actions(
460
+ images, img_masks, lang_tokens, lang_masks, state, noise=noise
461
+ )
462
+
463
+ # Unpad actions
464
+ original_action_dim = self.config.action_feature.shape[0]
465
+ actions = actions[:, :, :original_action_dim]
466
+ actions = self.unnormalize_outputs({ACTION: actions})[ACTION]
467
+
468
+ if self.config.adapt_to_pi_aloha:
469
+ actions = self._pi_aloha_encode_actions(actions)
470
+
471
+ inference_time = time.perf_counter() - tick
472
+ if "dt_scale" in batch and batch["dt_scale"] > 1.0:
473
+ # IMPORTANT: To align the inference time with the simulation time
474
+ sleep_time = inference_time * (batch["dt_scale"] - 1)
475
+ logging.info(
476
+ "[Step%03d] Inference Time: %.4fs; Sleep Time: %.4fs"
477
+ % (batch["index"], inference_time, sleep_time)
478
+ )
479
+ time.sleep(sleep_time)
480
+
481
+ return actions
482
+
483
+ def _prepare_batch(self, batch: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
484
+ if self.config.adapt_to_pi_aloha:
485
+ batch[OBS_STATE] = self._pi_aloha_decode_state(batch[OBS_STATE])
486
+
487
+ return self.normalize_inputs(batch)
488
+
489
+ @torch.no_grad()
490
+ def predict_action_chunk(
491
+ self, batch: dict[str, torch.Tensor], noise: torch.Tensor | None = None
492
+ ) -> torch.Tensor:
493
+ self.eval()
494
+ batch = self._prepare_batch(batch)
495
+ self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
496
+
497
+ actions = self._get_action_chunk(batch, noise)
498
+ return actions
499
+
500
+ @torch.no_grad()
501
+ def select_action(
502
+ self, batch: dict[str, torch.Tensor], noise: torch.Tensor | None = None
503
+ ) -> torch.Tensor:
504
+ if self.config.enable_streaming:
505
+ return self._get_streaming_action(batch, noise)
506
+ else:
507
+ return self._get_non_streaming_action(batch, noise)
508
+
509
+ @torch.no_grad()
510
+ def _get_streaming_action(
511
+ self, batch: dict[str, torch.Tensor], noise: torch.Tensor | None = None
512
+ ) -> torch.Tensor:
513
+ # NOTE: This function does not do any GPU computation.
514
+ assert "index" in batch
515
+ # Put the latest observation into the input dict
516
+ self.q_in.update({"obs": (batch, noise)})
517
+
518
+ actions = None
519
+ if not self.q_out.empty():
520
+ actions = self.q_out.get_nowait()
521
+
522
+ # Merge actions into the queue
523
+ if actions is not None:
524
+ assert actions["actions"].size(0) == self.config.n_action_steps
525
+ skip_n_actions = batch["index"] - actions["index"]
526
+ logging.debug(
527
+ "Curr. Step: %03d; Act. Step: %03d; Skip Steps: %03d"
528
+ % (batch["index"], actions["index"], skip_n_actions)
529
+ )
530
+
531
+ actions["actions"] = actions["actions"][skip_n_actions:]
532
+ actions["index"] += skip_n_actions
533
+
534
+ prev_action_chunk = list(self._queues[ACTION])
535
+ curr_action_chunk = [
536
+ {"index": actions["index"] + i, "action": a}
537
+ for i, a in enumerate(actions["actions"])
538
+ ]
539
+ if not prev_action_chunk:
540
+ # The action queue is empty
541
+ self._queues[ACTION].extend(
542
+ [a for a in curr_action_chunk if a["index"] > self._action_index]
543
+ )
544
+ else:
545
+ self._queues[ACTION].clear()
546
+ prev_index_start = prev_action_chunk[0]["index"]
547
+ prev_index_end = prev_action_chunk[-1]["index"]
548
+ curr_index_start = curr_action_chunk[0]["index"]
549
+ if curr_index_start > prev_index_end:
550
+ self._queues[ACTION].extend(curr_action_chunk)
551
+ elif curr_index_start > prev_index_start:
552
+ keeplen = curr_index_start - prev_index_end
553
+ self._queues[ACTION].extend(
554
+ prev_action_chunk[:keeplen] + curr_action_chunk
555
+ )
556
+ elif curr_index_start <= prev_index_start:
557
+ droplen = prev_index_start - curr_index_start
558
+ self._queues[ACTION].extend(curr_action_chunk[droplen:])
559
+
560
+ if len(self._queues[ACTION]) == 0:
561
+ return None
562
+ else:
563
+ action = self._queues[ACTION].popleft()
564
+ self._action_index = action["index"]
565
+ return action["action"]
566
+
567
+ @torch.no_grad()
568
+ def _get_non_streaming_action(
569
+ self, batch: dict[str, torch.Tensor], noise: torch.Tensor | None = None
570
+ ) -> torch.Tensor:
571
+ self.eval()
572
+ # Save the state before normalization
573
+ latest_state = batch[OBS_STATE][:, -1:, :]
574
+
575
+ batch = self._prepare_batch(batch)
576
+ self._queues = populate_queues(self._queues, batch, exclude_keys=[ACTION])
577
+ # Action queue logic for n_action_steps > 1. When the action_queue is depleted,
578
+ # populate it by querying the policy.
579
+ if len(self._queues[ACTION]) == 0:
580
+ actions = self._get_action_chunk(batch, noise)
581
+ # `self.predict_action_chunk` returns a (batch_size, n_action_steps, action_dim)
582
+ # torch.Tensor, but the queue effectively has shape (n_action_steps, batch_size, *),
583
+ # hence the transpose.
584
+ if self.config.use_delta_action:
585
+ action_dim = actions.shape[-1] - 1
586
+ actions[..., :action_dim] += latest_state[..., :action_dim]
587
+
588
+ self._queues[ACTION].extend(
589
+ actions.transpose(0, 1)[: self.config.n_action_steps]
590
+ )
591
+
592
+ return self._queues[ACTION].popleft()
593
+
594
+ def forward(
595
+ self, batch: dict[str, torch.Tensor], noise=None, time=None
596
+ ) -> dict[str, torch.Tensor]:
597
+ """Do a full training forward pass to compute the loss"""
598
+ if self.config.adapt_to_pi_aloha:
599
+ batch[OBS_STATE] = self._pi_aloha_decode_state(batch[OBS_STATE])
600
+ batch[ACTION] = self._pi_aloha_encode_actions_inv(batch[ACTION])
601
+
602
+ batch = self.normalize_inputs(batch)
603
+ batch = self.normalize_targets(batch)
604
+ images, img_masks = self.prepare_images(batch)
605
+ state = self.prepare_state(batch)
606
+ lang_tokens, lang_masks = self.prepare_language(batch)
607
+
608
+ actions = self.prepare_action(batch)
609
+ actions_is_pad = batch.get("actions_id_pad")
610
+ loss_dict = {}
611
+ losses = self.model.forward(
612
+ images, img_masks, lang_tokens, lang_masks, state, actions, noise, time
613
+ )
614
+ loss_dict["losses_after_forward"] = losses.clone()
615
+ if actions_is_pad is not None:
616
+ in_episode_bound = ~actions_is_pad
617
+ losses = losses * in_episode_bound.unsqueeze(-1)
618
+ loss_dict["losses_after_in_ep_bound"] = losses.clone()
619
+
620
+ # Remove padding
621
+ losses = losses[:, :, : self.config.max_action_dim]
622
+ loss_dict["losses_after_rm_padding"] = losses.clone()
623
+
624
+ # For backward pass
625
+ loss = losses.mean()
626
+ # For backward pass
627
+ loss_dict["loss"] = loss.item()
628
+ return loss, loss_dict
629
+
630
+ def prepare_images(self, batch):
631
+ """Apply DynamicVLA preprocessing to the images, like resizing to 224x224 and padding to keep aspect ratio, and
632
+ convert pixel range from [0.0, 1.0] to [-1.0, 1.0] as requested by SigLIP.
633
+ """
634
+ images = []
635
+ img_masks = []
636
+ present_img_keys = [key for key in self.config.image_features if key in batch]
637
+ missing_img_keys = [
638
+ key for key in self.config.image_features if key not in batch
639
+ ]
640
+
641
+ if len(present_img_keys) == 0:
642
+ raise ValueError(
643
+ "All image features are missing from the batch. At least one expected. "
644
+ f"(batch: {batch.keys()}) (image_features:{self.config.image_features})"
645
+ )
646
+ # Preprocess image features present in the batch
647
+ for key in present_img_keys:
648
+ imgs = batch[key][:, None, :, :, :] if batch[key].ndim == 4 else batch[key]
649
+ b, n, c, h, w = imgs.shape
650
+ assert n == self.config.n_obs_steps
651
+ img = imgs.view(b, n * c, h, w)
652
+ if self.config.resize_imgs_with_padding is not None:
653
+ img = resize_with_pad(
654
+ img, *self.config.resize_imgs_with_padding, pad_value=0
655
+ )
656
+ # Normalize from range [0,1] to [-1,1] as expacted by siglip
657
+ img = img * 2.0 - 1.0
658
+ if f"{key}_padding_mask" in batch:
659
+ mask = batch[f"{key}_padding_mask"].bool()
660
+ else:
661
+ mask = torch.ones(img.shape[0], dtype=torch.bool, device=img.device)
662
+
663
+ images.append(img)
664
+ img_masks.append(mask)
665
+
666
+ # Create image features not present in the batch
667
+ # as fully 0 padded images.
668
+ for num_empty_cameras in range(len(missing_img_keys)):
669
+ if num_empty_cameras >= self.config.empty_cameras:
670
+ break
671
+
672
+ img = torch.ones_like(img) * -1
673
+ mask = torch.zeros_like(mask)
674
+ images.append(img)
675
+ img_masks.append(mask)
676
+
677
+ return images, img_masks
678
+
679
+ def prepare_language(self, batch) -> tuple[torch.Tensor, torch.Tensor]:
680
+ """Tokenize the text input"""
681
+ device = batch[OBS_STATE].device
682
+ tasks = batch["task"]
683
+ if isinstance(tasks, str):
684
+ tasks = [tasks]
685
+
686
+ if len(tasks) == 1:
687
+ tasks = [tasks[0] for _ in range(batch[OBS_STATE].shape[0])]
688
+
689
+ tasks = [task if task.endswith("\n") else f"{task}\n" for task in tasks]
690
+ tokenized_prompt = self.language_tokenizer.__call__(
691
+ tasks,
692
+ padding=self.config.pad_language_to,
693
+ padding_side="right",
694
+ max_length=self.config.tokenizer_max_length,
695
+ return_tensors="pt",
696
+ )
697
+ lang_tokens = tokenized_prompt["input_ids"].to(device=device)
698
+ lang_masks = tokenized_prompt["attention_mask"].to(
699
+ device=device, dtype=torch.bool
700
+ )
701
+ return lang_tokens, lang_masks
702
+
703
+ def _pi_aloha_decode_state(self, state):
704
+ # Flip the joints.
705
+ for motor_idx in [1, 2, 8, 9]:
706
+ state[:, motor_idx] *= -1
707
+ # Reverse the gripper transformation that is being applied by the Aloha runtime.
708
+ for motor_idx in [6, 13]:
709
+ state[:, motor_idx] = aloha_gripper_to_angular(state[:, motor_idx])
710
+
711
+ return state
712
+
713
+ def _pi_aloha_encode_actions(self, actions):
714
+ # Flip the joints.
715
+ for motor_idx in [1, 2, 8, 9]:
716
+ actions[:, :, motor_idx] *= -1
717
+ # Reverse the gripper transformation that is being applied by the Aloha runtime.
718
+ for motor_idx in [6, 13]:
719
+ actions[:, :, motor_idx] = aloha_gripper_from_angular(
720
+ actions[:, :, motor_idx]
721
+ )
722
+ return actions
723
+
724
+ def _pi_aloha_encode_actions_inv(self, actions):
725
+ # Flip the joints again.
726
+ for motor_idx in [1, 2, 8, 9]:
727
+ actions[:, :, motor_idx] *= -1
728
+ # Reverse the gripper transformation that is being applied by the Aloha runtime.
729
+ for motor_idx in [6, 13]:
730
+ actions[:, :, motor_idx] = aloha_gripper_from_angular_inv(
731
+ actions[:, :, motor_idx]
732
+ )
733
+ return actions
734
+
735
+ def prepare_state(self, batch):
736
+ """Pad state"""
737
+ state = (
738
+ batch[OBS_STATE][:, -1, :]
739
+ if batch[OBS_STATE].ndim > 2
740
+ else batch[OBS_STATE]
741
+ )
742
+ state = pad_vector(state, self.config.max_state_dim)
743
+ return state
744
+
745
+ def prepare_action(self, batch):
746
+ """Pad action"""
747
+ actions = pad_vector(batch[ACTION], self.config.max_action_dim)
748
+ return actions
749
+
750
+
751
+ def pad_tensor(tensor, max_len, pad_value=0):
752
+ """
753
+ Efficiently pads a torch.Tensor along sequence dimension to match max_len.
754
+
755
+ Args:
756
+ torch.Tensor (torch.Tensor): Shape (B, L, ...) or (B, L).
757
+ max_len (int): Fixed sequence length.
758
+ pad_value (int/float): Value for padding.
759
+
760
+ Returns:
761
+ torch.Tensor: Shape (B, max_len, ...) or (B, max_len).
762
+ """
763
+ b, d = torch.Tensor.shape[:2]
764
+
765
+ # Create a padded torch.Tensor of max_len and copy the existing values
766
+ padded_tensor = torch.full(
767
+ (b, max_len, *tensor.shape[2:]),
768
+ pad_value,
769
+ dtype=tensor.dtype,
770
+ device=tensor.device,
771
+ )
772
+ padded_tensor[:, :d] = tensor # Efficient in-place copy
773
+
774
+ return padded_tensor
775
+
776
+
777
+ class VLAFlowMatching(torch.nn.Module):
778
+ """
779
+ ┌──────────────────────────────┐
780
+ │ actions │
781
+ │ ▲ │
782
+ │ ┌─────────┐ ┌─|────┐ │
783
+ │ | │────► │ │ │
784
+ │ | │ kv │ │ │
785
+ │ | │────► │Action│ │
786
+ │ | VLM │cache │Expert│ |
787
+ │ │ │────► | │ │
788
+ │ │ │ │ │ │
789
+ │ └▲──▲───▲─┘ └───▲──┘ |
790
+ │ │ | | │ |
791
+ │ | | | noise │
792
+ │ │ │ state │
793
+ │ │ language tokens │
794
+ │ image(s) │
795
+ └──────────────────────────────┘
796
+ """
797
+
798
+ def __init__(self, config: DynamicVLAConfig):
799
+ super().__init__()
800
+ self.config = config
801
+
802
+ if config.temporal_fusion == "conv":
803
+ self.mults_proj = torch.nn.Sequential(
804
+ torch.nn.Conv2d(3 * config.n_obs_steps, 3, kernel_size=7, padding=3),
805
+ torch.nn.GELU(),
806
+ )
807
+ vlm_input_channels = 3
808
+ elif config.temporal_fusion == "attn":
809
+ vlm_input_channels = 3 * config.n_obs_steps
810
+ elif config.temporal_fusion == "flat":
811
+ vlm_input_channels = 3
812
+ else:
813
+ raise ValueError(f"Unknown temporal_fusion: {config.temporal_fusion}")
814
+
815
+ self.vlm_with_expert: VLMWithExpertModel = self._get_vlm_with_expert(
816
+ config, config.vlm_model_name, vlm_input_channels
817
+ )
818
+ self.state_proj = torch.nn.Linear(
819
+ config.max_state_dim,
820
+ self.vlm_with_expert.vlm_config.text_config.hidden_size,
821
+ )
822
+ self.action_in_proj = torch.nn.Linear(
823
+ config.max_action_dim, self.vlm_with_expert.expert_hidden_size
824
+ )
825
+ self.action_out_proj = torch.nn.Linear(
826
+ self.vlm_with_expert.expert_hidden_size, config.max_action_dim
827
+ )
828
+ self.action_time_mlp_in = torch.nn.Linear(
829
+ self.vlm_with_expert.expert_hidden_size * 2,
830
+ self.vlm_with_expert.expert_hidden_size,
831
+ )
832
+ self.action_time_mlp_out = torch.nn.Linear(
833
+ self.vlm_with_expert.expert_hidden_size,
834
+ self.vlm_with_expert.expert_hidden_size,
835
+ )
836
+ self._set_requires_grad()
837
+
838
+ def _get_vlm_with_expert(
839
+ self,
840
+ config: DynamicVLAConfig,
841
+ vlm_model_name: str,
842
+ vlm_input_channels: int,
843
+ ):
844
+ if vlm_model_name.startswith("HuggingFaceTB/SmolVLM2"):
845
+ vlm_config = AutoConfig.from_pretrained(vlm_model_name)
846
+ vlm_config.vision_config.num_channels = vlm_input_channels
847
+ vlm_config.vision_config.patch_size = config.smolvlm_patch_size
848
+ vlm_config.vision_config.num_attention_heads = (
849
+ config.smolvlm_attention_heads
850
+ )
851
+ vlm_config.vision_config.hidden_size = config.smolvlm_hidden_size
852
+ vlm_config.vision_config.intermediate_size = (
853
+ config.smolvlm_intermediate_size
854
+ )
855
+ vlm = SmolVLMForConditionalGeneration(config=vlm_config)
856
+ elif vlm_model_name.startswith("HuggingFaceTB/SmolLM2"):
857
+ text_config = AutoConfig.from_pretrained(vlm_model_name)
858
+ vision_config = FastViTConfig(
859
+ in_channels=vlm_input_channels,
860
+ position_embeddings=[
861
+ None,
862
+ None,
863
+ None,
864
+ {"name": "RepCPE", "spatial_shape": (7, 7)},
865
+ {"name": "RepCPE", "spatial_shape": (7, 7)},
866
+ ],
867
+ inference_mode=config.fastvlm_inference_mode,
868
+ )
869
+ vlm = FastVLMForConditionalGeneration(
870
+ config=FastVLMConfig(
871
+ text_config=text_config,
872
+ vision_config=vision_config,
873
+ )
874
+ )
875
+ elif vlm_model_name.startswith("Qwen/Qwen2"):
876
+ text_config = AutoConfig.from_pretrained(vlm_model_name)
877
+ vision_config = FastViTConfig(
878
+ in_channels=vlm_input_channels,
879
+ position_embeddings=[
880
+ None,
881
+ None,
882
+ None,
883
+ {"name": "RepCPE", "spatial_shape": (7, 7)},
884
+ {"name": "RepCPE", "spatial_shape": (7, 7)},
885
+ ],
886
+ inference_mode=config.fastvlm_inference_mode,
887
+ )
888
+ vlm = FastVLMForConditionalGeneration(
889
+ config=FastVLMConfig(
890
+ text_config=text_config,
891
+ vision_config=vision_config,
892
+ )
893
+ )
894
+ else:
895
+ raise ValueError(f"Unknown VLM: {vlm_model_name}")
896
+
897
+ return VLMWithExpertModel(
898
+ model_id=config.vlm_model_name,
899
+ vlm=vlm,
900
+ freeze_vision_model=config.freeze_vision_model,
901
+ freeze_connector=config.freeze_connector,
902
+ freeze_text_model=config.freeze_text_model,
903
+ num_vlm_layers=self.config.num_vlm_layers,
904
+ num_expert_layers=config.num_expert_layers,
905
+ num_expert_skip_layers=config.num_expert_skip_layers,
906
+ attention_mode=config.attention_mode,
907
+ self_attn_every_n_layers=self.config.self_attn_every_n_layers,
908
+ expert_width_multiplier=self.config.expert_width_multiplier,
909
+ )
910
+
911
+ def _set_requires_grad(self):
912
+ for params in self.state_proj.parameters():
913
+ params.requires_grad = self.config.train_state_proj
914
+
915
+ def _sample_noise(self, shape, device, dtype=torch.float32):
916
+ noise = torch.normal(
917
+ mean=0.0,
918
+ std=1.0,
919
+ size=shape,
920
+ dtype=dtype,
921
+ device=device,
922
+ )
923
+ return noise
924
+
925
+ def _sample_time(self, bsize, device, dtype=torch.float32):
926
+ beta_dist = torch.distributions.Beta(concentration1=1.5, concentration0=1.0)
927
+ time_beta = beta_dist.sample((bsize,)).to(device=device, dtype=dtype)
928
+ time = time_beta * 0.999 + 0.001
929
+ return time
930
+
931
+ def _embed_prefix(
932
+ self, images, img_masks, lang_tokens, lang_masks, state: torch.Tensor = None
933
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
934
+ embs = []
935
+ pad_masks = []
936
+ att_masks = []
937
+ for img, img_mask in zip(images, img_masks, strict=False):
938
+ bsize = img.size(0)
939
+ if self.config.temporal_fusion == "conv":
940
+ # Temporal fusion of image frames with Conv2d
941
+ img = self.mults_proj(img)
942
+ elif self.config.temporal_fusion == "flat":
943
+ # Flatten temporal dimension into batch dimension
944
+ img = img.view(-1, 3, img.shape[2], img.shape[3])
945
+
946
+ img_emb = self.vlm_with_expert.embed_image(img)
947
+ img_emb_dim = img_emb.size(-1)
948
+ if self.config.temporal_fusion == "flat":
949
+ # Reshape back to (batch_size, n_obs_steps * 3, emb_dim)
950
+ img_emb = img_emb.view(bsize, -1, img_emb_dim)
951
+
952
+ # Normalize image embeddings
953
+ img_emb = img_emb * torch.tensor(
954
+ img_emb_dim**0.5, dtype=img_emb.dtype, device=img_emb.device
955
+ )
956
+ num_img_embs = img_emb.size(1)
957
+ img_mask = img_mask[:, None].expand(bsize, num_img_embs)
958
+ embs.append(img_emb)
959
+ pad_masks.append(img_mask)
960
+ att_masks += [0] * (num_img_embs)
961
+
962
+ lang_emb = self.vlm_with_expert.embed_language_tokens(lang_tokens)
963
+ # Normalize language embeddings
964
+ lang_emb_dim = lang_emb.shape[-1]
965
+ lang_emb = lang_emb * math.sqrt(lang_emb_dim)
966
+ embs.append(lang_emb)
967
+ pad_masks.append(lang_masks)
968
+ num_lang_embs = lang_emb.shape[1]
969
+ att_masks += [0] * num_lang_embs
970
+
971
+ if state is not None:
972
+ state_emb = self.state_proj(state)
973
+ state_emb = state_emb[:, None, :] if state_emb.ndim == 2 else state_emb
974
+ embs.append(state_emb)
975
+ states_seq_len = state_emb.shape[1]
976
+ state_mask = torch.ones(
977
+ state_emb.shape[0],
978
+ states_seq_len,
979
+ dtype=torch.bool,
980
+ device=state_emb.device,
981
+ )
982
+ pad_masks.append(state_mask)
983
+ # Set attention masks so that image and language inputs do not attend to state or actions
984
+ att_masks += [1] * (states_seq_len)
985
+
986
+ embs = torch.cat(embs, dim=1)
987
+ pad_masks = torch.cat(pad_masks, dim=1)
988
+ att_masks = torch.tensor(att_masks, dtype=torch.bool, device=pad_masks.device)
989
+ att_masks = att_masks[None, :]
990
+
991
+ seq_len = pad_masks.shape[1]
992
+ if seq_len < self.config.prefix_length:
993
+ embs = pad_tensor(embs, self.config.prefix_length, pad_value=0)
994
+ pad_masks = pad_tensor(pad_masks, self.config.prefix_length, pad_value=0)
995
+ att_masks = pad_tensor(att_masks, self.config.prefix_length, pad_value=0)
996
+
997
+ att_masks = att_masks.expand(bsize, -1)
998
+ return embs, pad_masks, att_masks
999
+
1000
+ def _embed_suffix(self, noisy_actions, timestep):
1001
+ embs = []
1002
+ pad_masks = []
1003
+ att_masks = []
1004
+
1005
+ # Fuse timestep + action information using an MLP
1006
+ action_emb = self.action_in_proj(noisy_actions)
1007
+ device = action_emb.device
1008
+ dtype = action_emb.dtype
1009
+ # Embed timestep using sine-cosine positional encoding with sensitivity in the range [0, 1]
1010
+ time_emb = create_sinusoidal_pos_embedding(
1011
+ timestep,
1012
+ self.vlm_with_expert.expert_hidden_size,
1013
+ self.config.min_period,
1014
+ self.config.max_period,
1015
+ device=device,
1016
+ )
1017
+ time_emb = time_emb.type(dtype=dtype)
1018
+
1019
+ time_emb = time_emb[:, None, :].expand_as(action_emb)
1020
+ action_time_emb = torch.cat([action_emb, time_emb], dim=2)
1021
+
1022
+ action_time_emb = self.action_time_mlp_in(action_time_emb)
1023
+ action_time_emb = F.silu(action_time_emb) # swish == silu
1024
+ action_time_emb = self.action_time_mlp_out(action_time_emb)
1025
+
1026
+ # Add to input tokens
1027
+ embs.append(action_time_emb)
1028
+
1029
+ bsize, action_time_dim = action_time_emb.shape[:2]
1030
+ action_time_mask = torch.ones(
1031
+ bsize, action_time_dim, dtype=torch.bool, device=device
1032
+ )
1033
+ pad_masks.append(action_time_mask)
1034
+
1035
+ # Set attention masks so that image, language and state inputs do not attend to action tokens
1036
+ att_masks += [1] * self.config.chunk_size
1037
+ embs = torch.cat(embs, dim=1)
1038
+ pad_masks = torch.cat(pad_masks, dim=1)
1039
+ att_masks = torch.tensor(att_masks, dtype=embs.dtype, device=embs.device)
1040
+ att_masks = att_masks[None, :].expand(bsize, len(att_masks))
1041
+ return embs, pad_masks, att_masks
1042
+
1043
+ def _get_position_ids(
1044
+ self,
1045
+ prefix_offsets: torch.Tensor | None,
1046
+ pad_masks: torch.Tensor,
1047
+ ) -> torch.Tensor:
1048
+ position_ids = torch.cumsum(pad_masks, dim=1) - 1
1049
+ if prefix_offsets is not None:
1050
+ position_ids += prefix_offsets
1051
+
1052
+ return position_ids
1053
+
1054
+ def forward(
1055
+ self,
1056
+ images,
1057
+ img_masks,
1058
+ lang_tokens,
1059
+ lang_masks,
1060
+ state,
1061
+ actions,
1062
+ noise=None,
1063
+ time=None,
1064
+ ) -> torch.Tensor:
1065
+ """Do a full training forward pass and compute the loss (batch_size x num_steps x num_motors)"""
1066
+ if noise is None:
1067
+ noise = self._sample_noise(actions.shape, actions.device)
1068
+
1069
+ if time is None:
1070
+ time = self._sample_time(actions.shape[0], actions.device)
1071
+
1072
+ time_expanded = time[:, None, None]
1073
+ x_t = time_expanded * noise + (1 - time_expanded) * actions
1074
+ u_t = noise - actions
1075
+ prefix_embs, prefix_pad_masks, prefix_att_masks = self._embed_prefix(
1076
+ images, img_masks, lang_tokens, lang_masks, state=state
1077
+ )
1078
+ suffix_embs, suffix_pad_masks, suffix_att_masks = self._embed_suffix(x_t, time)
1079
+
1080
+ pad_masks = torch.cat([prefix_pad_masks, suffix_pad_masks], dim=1)
1081
+ att_masks = torch.cat([prefix_att_masks, suffix_att_masks], dim=1)
1082
+ att_2d_masks = make_att_2d_masks(pad_masks, att_masks)
1083
+ position_ids = self._get_position_ids(None, pad_masks)
1084
+
1085
+ (_, suffix_out), _ = self.vlm_with_expert(
1086
+ attention_mask=att_2d_masks,
1087
+ position_ids=position_ids,
1088
+ past_key_values=None,
1089
+ inputs_embeds=[prefix_embs, suffix_embs],
1090
+ use_cache=False,
1091
+ fill_kv_cache=False,
1092
+ )
1093
+ suffix_out = suffix_out[:, -self.config.chunk_size :]
1094
+ # Original openpi code, upcast attention output
1095
+ suffix_out = suffix_out.to(dtype=torch.float32)
1096
+ v_t = self.action_out_proj(suffix_out)
1097
+ losses = F.mse_loss(u_t, v_t, reduction="none")
1098
+ return losses
1099
+
1100
+ def sample_vlm_embedding(
1101
+ self, images, img_masks, lang_tokens, lang_masks, state
1102
+ ) -> torch.Tensor:
1103
+ """Do a half inference forward and compute the VLM embedding"""
1104
+
1105
+ prefix_embs, prefix_pad_masks, prefix_att_masks = self._embed_prefix(
1106
+ images, img_masks, lang_tokens, lang_masks, state
1107
+ )
1108
+ prefix_att_2d_masks = make_att_2d_masks(prefix_pad_masks, prefix_att_masks)
1109
+ prefix_position_ids = self._get_position_ids(None, prefix_pad_masks)
1110
+ # Compute image and language key value cache
1111
+ _, past_key_values = self.vlm_with_expert.forward(
1112
+ attention_mask=prefix_att_2d_masks,
1113
+ position_ids=prefix_position_ids,
1114
+ past_key_values=None,
1115
+ inputs_embeds=[prefix_embs, None],
1116
+ use_cache=self.config.use_cache,
1117
+ fill_kv_cache=True,
1118
+ )
1119
+ return prefix_pad_masks, past_key_values
1120
+
1121
+ def sample_actions(
1122
+ self, images, img_masks, lang_tokens, lang_masks, state, noise=None
1123
+ ) -> torch.Tensor:
1124
+ """Do a full inference forward and compute the action (batch_size x num_steps x num_motors)"""
1125
+ bsize = state.shape[0]
1126
+ device = state.device
1127
+ if noise is None:
1128
+ actions_shape = (bsize, self.config.chunk_size, self.config.max_action_dim)
1129
+ noise = self._sample_noise(actions_shape, device)
1130
+
1131
+ prefix_pad_masks, past_key_values = self.sample_vlm_embedding(
1132
+ images, img_masks, lang_tokens, lang_masks, state
1133
+ )
1134
+ dt = -1.0 / self.config.num_steps
1135
+ dt = torch.tensor(dt, dtype=torch.float32, device=device)
1136
+
1137
+ x_t = noise
1138
+ time = torch.tensor(1.0, dtype=torch.float32, device=device)
1139
+ while time >= -dt / 2:
1140
+ expanded_time = time.expand(bsize)
1141
+ v_t = self.denoise_step(
1142
+ prefix_pad_masks,
1143
+ past_key_values,
1144
+ x_t,
1145
+ expanded_time,
1146
+ )
1147
+ # Euler step
1148
+ x_t += dt * v_t
1149
+ time += dt
1150
+
1151
+ return x_t
1152
+
1153
+ def denoise_step(
1154
+ self,
1155
+ prefix_pad_masks,
1156
+ past_key_values,
1157
+ x_t,
1158
+ timestep,
1159
+ ):
1160
+ """Apply one denoising step of the noise `x_t` at a given timestep."""
1161
+ suffix_embs, suffix_pad_masks, suffix_att_masks = self._embed_suffix(
1162
+ x_t, timestep
1163
+ )
1164
+
1165
+ suffix_len = suffix_pad_masks.shape[1]
1166
+ batch_size = prefix_pad_masks.shape[0]
1167
+ prefix_len = prefix_pad_masks.shape[1]
1168
+ prefix_pad_2d_masks = prefix_pad_masks[:, None, :].expand(
1169
+ batch_size, suffix_len, prefix_len
1170
+ )
1171
+
1172
+ suffix_att_2d_masks = make_att_2d_masks(suffix_pad_masks, suffix_att_masks)
1173
+ full_att_2d_masks = torch.cat([prefix_pad_2d_masks, suffix_att_2d_masks], dim=2)
1174
+ prefix_offsets = torch.sum(prefix_pad_masks, dim=-1)[:, None]
1175
+ position_ids = self._get_position_ids(prefix_offsets, suffix_pad_masks)
1176
+
1177
+ outputs_embeds, _ = self.vlm_with_expert.forward(
1178
+ attention_mask=full_att_2d_masks,
1179
+ position_ids=position_ids,
1180
+ past_key_values=past_key_values,
1181
+ inputs_embeds=[None, suffix_embs],
1182
+ use_cache=self.config.use_cache,
1183
+ fill_kv_cache=False,
1184
+ )
1185
+ suffix_out = outputs_embeds[1]
1186
+ suffix_out = suffix_out[:, -self.config.chunk_size :]
1187
+ suffix_out = suffix_out.to(dtype=torch.float32)
1188
+ v_t = self.action_out_proj(suffix_out)
1189
+ return v_t
policies/dynamicvla/modeling_fastvlm.py ADDED
@@ -0,0 +1,1635 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # @File: modeling_fastvlm.py
4
+ # @Author: Haozhe Xie
5
+ # @Date: 2025-09-10 20:19:11
6
+ # @Last Modified by: Haozhe Xie
7
+ # @Last Modified at: 2026-02-26 10:12:16
8
+ # @Email: root@haozhexie.com
9
+
10
+ import functools
11
+ import logging
12
+ import typing
13
+
14
+ import torch
15
+ import torch.nn.functional as F
16
+ from timm.layers import DropPath, SqueezeExcite
17
+ from transformers import (
18
+ AutoModel,
19
+ GenerationMixin,
20
+ LlamaConfig,
21
+ LlamaModel,
22
+ PretrainedConfig,
23
+ PreTrainedModel,
24
+ Qwen2Config,
25
+ )
26
+ from transformers.cache_utils import Cache, DynamicCache
27
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
28
+ from transformers.modeling_outputs import ModelOutput
29
+ from transformers.processing_utils import Unpack
30
+ from transformers.utils import TransformersKwargs
31
+
32
+
33
+ class KwargsForCausalLM(FlashAttentionKwargs, TransformersKwargs): ...
34
+
35
+
36
+ class FastVLMBaseModelOutputWithPast(ModelOutput):
37
+ last_hidden_state: typing.Optional[torch.FloatTensor] = None
38
+ past_key_values: typing.Optional[Cache] = None
39
+ hidden_states: typing.Optional[tuple[torch.FloatTensor]] = None
40
+ attentions: typing.Optional[tuple[torch.FloatTensor]] = None
41
+ image_hidden_states: typing.Optional[tuple[torch.FloatTensor]] = None
42
+
43
+
44
+ class FastVLMCausalLMOutputWithPast(ModelOutput):
45
+ loss: typing.Optional[torch.FloatTensor] = None
46
+ logits: typing.Optional[torch.FloatTensor] = None
47
+ past_key_values: typing.Optional[Cache] = None
48
+ hidden_states: typing.Optional[tuple[torch.FloatTensor]] = None
49
+ attentions: typing.Optional[tuple[torch.FloatTensor]] = None
50
+ image_hidden_states: typing.Optional[tuple[torch.FloatTensor]] = None
51
+
52
+
53
+ class FastViTConfig(PretrainedConfig):
54
+ model_type = "fastvit"
55
+ base_config_key = "vision_config"
56
+
57
+ def __init__(
58
+ self,
59
+ in_channels=3,
60
+ out_channels=768,
61
+ image_size=1024,
62
+ patch_size=64,
63
+ n_blocks=[2, 12, 24, 4, 2],
64
+ embed_dims=[96, 192, 384, 768, 1536],
65
+ mlp_ratios=[4, 4, 4, 4, 4],
66
+ downsample=[True, True, True, True, True],
67
+ downsample_patch_size=7,
68
+ downsample_stride=2,
69
+ downsample_use_se=None,
70
+ position_embeddings=None,
71
+ token_mixers=("repmixer", "repmixer", "repmixer", "attention", "attention"),
72
+ repmixer_kernel_size=3,
73
+ use_scale_branch=True,
74
+ use_layer_scale=True,
75
+ drop_path_rate=0.0,
76
+ layer_scale_init_value=1e-5,
77
+ clsss_ratio=2,
78
+ inference_mode=False,
79
+ **kwargs,
80
+ ) -> None:
81
+ super().__init__(**kwargs)
82
+ self.in_channels = in_channels
83
+ self.out_channels = out_channels
84
+ self.image_size = image_size
85
+ self.patch_size = patch_size
86
+ self.n_blocks = n_blocks
87
+ self.embed_dims = embed_dims
88
+ self.mlp_ratios = mlp_ratios
89
+ self.downsample = downsample
90
+ self.downsample_patch_size = downsample_patch_size
91
+ self.downsample_stride = downsample_stride
92
+ self.downsample_use_se = downsample_use_se
93
+ self.position_embeddings = position_embeddings
94
+ self.token_mixers = token_mixers
95
+ self.repmixer_kernel_size = repmixer_kernel_size
96
+ self.use_scale_branch = use_scale_branch
97
+ self.use_layer_scale = use_layer_scale
98
+ self.drop_path_rate = drop_path_rate
99
+ self.layer_scale_init_value = layer_scale_init_value
100
+ self.clsss_ratio = clsss_ratio
101
+ self.inference_mode = inference_mode
102
+
103
+
104
+ class FastVLMConfig(PretrainedConfig):
105
+
106
+ model_type = "fastvlm"
107
+ sub_configs = {"text_config": LlamaConfig | Qwen2Config, "vision_config": FastViTConfig}
108
+
109
+ def __init__(
110
+ self,
111
+ use_cache=True,
112
+ tie_word_embeddings=False,
113
+ max_position_embeddings=None,
114
+ text_config=None,
115
+ vision_config=None,
116
+ image_token_id=128_257,
117
+ pad_token_id=128_002,
118
+ **kwargs,
119
+ ) -> None:
120
+ # ViTConfig
121
+ if vision_config is None:
122
+ self.vision_config = FastViTConfig()
123
+ elif isinstance(vision_config, dict):
124
+ self.vision_config = FastViTConfig(**vision_config)
125
+ elif isinstance(vision_config, FastViTConfig):
126
+ self.vision_config = vision_config
127
+ else:
128
+ raise ValueError("No valid vision_config is provided.")
129
+
130
+ # LlamaConfig
131
+ if text_config is None:
132
+ self.text_config = LlamaConfig()
133
+ elif isinstance(text_config, dict):
134
+ self.text_config = LlamaConfig(**text_config)
135
+ elif isinstance(text_config, LlamaConfig) or isinstance(text_config, Qwen2Config):
136
+ self.text_config = text_config
137
+ else:
138
+ raise ValueError("No valid text_config is provided.")
139
+
140
+ self.use_cache = use_cache
141
+ self.image_token_id = image_token_id
142
+ self.tie_word_embeddings = tie_word_embeddings
143
+ if max_position_embeddings is not None:
144
+ self.text_config.max_position_embeddings = max_position_embeddings
145
+
146
+ super().__init__(
147
+ **kwargs, pad_token_id=pad_token_id, tie_word_embeddings=tie_word_embeddings
148
+ )
149
+
150
+
151
+ class FastVLMPreTrainedModel(PreTrainedModel):
152
+ config_class = FastVLMConfig
153
+ base_model_prefix = "model"
154
+ supports_gradient_checkpointing = True
155
+ _skip_keys_device_placement = "past_key_values"
156
+ _supports_flash_attn_2 = True
157
+ _supports_sdpa = True
158
+ _supports_flex_attn = True
159
+ _supports_cache_class = True
160
+ _supports_attention_backend = True
161
+
162
+ def _init_weights(self, module):
163
+ std = getattr(
164
+ self.config,
165
+ "initializer_range",
166
+ self.config.get_text_config().initializer_range,
167
+ )
168
+
169
+ if isinstance(module, (torch.nn.Linear, torch.nn.Conv2d)):
170
+ module.weight.data.normal_(mean=0.0, std=std)
171
+ if module.bias is not None:
172
+ module.bias.data.zero_()
173
+ elif isinstance(module, torch.nn.Embedding):
174
+ module.weight.data.normal_(mean=0.0, std=std)
175
+ if module.padding_idx is not None:
176
+ module.weight.data[module.padding_idx].zero_()
177
+ elif isinstance(module, torch.nn.LayerNorm):
178
+ module.weight.data.fill_(1.0)
179
+ module.bias.data.zero_()
180
+
181
+
182
+ class FastVLMForConditionalGeneration(FastVLMPreTrainedModel, GenerationMixin):
183
+ def __init__(self, config):
184
+ super().__init__(config)
185
+ self.model = FastVLMModel(config)
186
+ self.lm_head = torch.nn.Linear(
187
+ config.text_config.hidden_size, config.text_config.vocab_size, bias=False
188
+ )
189
+ self.vocab_size = config.text_config.vocab_size
190
+ # Initialize weights and apply final processing
191
+ self.post_init()
192
+
193
+ def enable_input_require_grads(self):
194
+ """
195
+ Enables the gradients for the input embeddings. This is useful for fine-tuning
196
+ adapter weights while keeping the model weights fixed.
197
+ """
198
+
199
+ def make_inputs_require_grads(module, input, output):
200
+ output.requires_grad_(True)
201
+
202
+ self._text_require_grads_hook = (
203
+ self.get_input_embeddings().register_forward_hook(make_inputs_require_grads)
204
+ )
205
+ self._vision_require_grads_hook = (
206
+ self.model.vision_model.get_input_embeddings().register_forward_hook(
207
+ make_inputs_require_grads
208
+ )
209
+ )
210
+
211
+ def disable_input_require_grads(self):
212
+ self._text_require_grads_hook.remove()
213
+ self._vision_require_grads_hook.remove()
214
+
215
+ def get_input_embeddings(self):
216
+ return self.model.text_model.get_input_embeddings()
217
+
218
+ def set_input_embeddings(self, value):
219
+ self.model.text_model.set_input_embeddings(value)
220
+
221
+ def get_output_embeddings(self):
222
+ return self.lm_head
223
+
224
+ def set_output_embeddings(self, new_embeddings):
225
+ self.lm_head = new_embeddings
226
+
227
+ def forward(
228
+ self,
229
+ input_ids: typing.Optional[torch.LongTensor] = None,
230
+ attention_mask: typing.Optional[torch.Tensor] = None,
231
+ position_ids: typing.Optional[torch.LongTensor] = None,
232
+ past_key_values: typing.Optional[typing.List[torch.FloatTensor]] = None,
233
+ inputs_embeds: typing.Optional[torch.FloatTensor] = None,
234
+ pixel_values: typing.Optional[torch.FloatTensor] = None,
235
+ pixel_attention_mask: typing.Optional[torch.BoolTensor] = None,
236
+ image_hidden_states: typing.Optional[torch.FloatTensor] = None,
237
+ labels: typing.Optional[torch.LongTensor] = None,
238
+ output_attentions: typing.Optional[bool] = None,
239
+ output_hidden_states: typing.Optional[bool] = None,
240
+ use_cache: typing.Optional[bool] = None,
241
+ cache_position: typing.Optional[torch.LongTensor] = None,
242
+ return_dict: typing.Optional[bool] = None,
243
+ logits_to_keep: typing.Union[int, torch.Tensor] = 0,
244
+ **kwargs: Unpack[KwargsForCausalLM],
245
+ ) -> typing.Union[typing.Tuple, FastVLMCausalLMOutputWithPast]:
246
+ output_attentions = (
247
+ output_attentions
248
+ if output_attentions is not None
249
+ else self.config.output_attentions
250
+ )
251
+ output_hidden_states = (
252
+ output_hidden_states
253
+ if output_hidden_states is not None
254
+ else self.config.output_hidden_states
255
+ )
256
+ return_dict = (
257
+ return_dict if return_dict is not None else self.config.use_return_dict
258
+ )
259
+
260
+ # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
261
+ outputs = self.model(
262
+ input_ids=input_ids,
263
+ attention_mask=attention_mask,
264
+ position_ids=position_ids,
265
+ past_key_values=past_key_values,
266
+ inputs_embeds=inputs_embeds,
267
+ pixel_values=pixel_values,
268
+ pixel_attention_mask=pixel_attention_mask,
269
+ image_hidden_states=image_hidden_states,
270
+ output_attentions=output_attentions,
271
+ output_hidden_states=output_hidden_states,
272
+ use_cache=use_cache,
273
+ cache_position=cache_position,
274
+ return_dict=return_dict,
275
+ **kwargs,
276
+ )
277
+
278
+ hidden_states = outputs[0]
279
+ # Only compute necessary logits, and do not upcast them to float if we are not
280
+ # computing the loss
281
+ slice_indices = (
282
+ slice(-logits_to_keep, None)
283
+ if isinstance(logits_to_keep, int)
284
+ else logits_to_keep
285
+ )
286
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
287
+
288
+ loss = None
289
+ if labels is not None:
290
+ loss = self.loss_function(
291
+ logits=logits,
292
+ labels=labels,
293
+ vocab_size=self.config.text_config.vocab_size,
294
+ **kwargs,
295
+ )
296
+
297
+ return FastVLMCausalLMOutputWithPast(
298
+ loss=loss,
299
+ logits=logits,
300
+ past_key_values=outputs.past_key_values,
301
+ hidden_states=outputs.hidden_states,
302
+ attentions=outputs.attentions,
303
+ image_hidden_states=outputs.image_hidden_states,
304
+ )
305
+
306
+
307
+ class FastVLMModel(FastVLMPreTrainedModel):
308
+ def __init__(self, config: FastVLMConfig) -> None:
309
+ super().__init__(config)
310
+ self.vision_model = FastViT(config.vision_config)
311
+ self.connector = FastVLMConnector(config)
312
+ self.text_model: LlamaModel = AutoModel.from_config(config.text_config)
313
+ self.post_init()
314
+
315
+ def enable_input_require_grads(self):
316
+ """
317
+ Enables the gradients for the input embeddings.
318
+
319
+ This is useful for lora when using gradient checkpointing.
320
+ c.f. https://github.com/huggingface/peft/issues/1402#issuecomment-1913675032
321
+
322
+ Override to set output.requires_grad = True for both the decoder's and vision model's
323
+ embeddings.
324
+ """
325
+
326
+ def get_lowest_module(module):
327
+ if len(list(module.children())) == 0:
328
+ # If the module has no children, it is a leaf module (e.g., Linear, Conv2d, etc.)
329
+ return module
330
+ else:
331
+ # Recursively call the function on each child module
332
+ return get_lowest_module(list(module.children())[0])
333
+
334
+ def make_inputs_require_grads(module, input, output):
335
+ output.requires_grad_(True)
336
+
337
+ self._text_require_grads_hook = (
338
+ self.get_input_embeddings().register_forward_hook(make_inputs_require_grads)
339
+ )
340
+ self._vision_require_grads_hook = get_lowest_module(
341
+ self.vision_model
342
+ ).register_forward_hook(make_inputs_require_grads)
343
+
344
+ def disable_input_require_grads(self) -> None:
345
+ self._text_require_grads_hook.remove()
346
+ self._vision_require_grads_hook.remove()
347
+
348
+ def get_input_embeddings(self) -> torch.nn.Module:
349
+ return self.text_model.get_input_embeddings()
350
+
351
+ def set_input_embeddings(self, value: torch.nn.Module) -> None:
352
+ self.text_model.set_input_embeddings(value)
353
+
354
+ def get_image_features(self, pixel_values: torch.FloatTensor):
355
+ batch_size, num_images, _, _, _ = pixel_values.shape
356
+ pixel_values = pixel_values.view(
357
+ batch_size * num_images, *pixel_values.shape[2:]
358
+ )
359
+ # Modality projection & resampling
360
+ image_features = self.vision_model(pixel_values).flatten(2).transpose(1, 2)
361
+ image_features = self.connector(image_features)
362
+ return image_features
363
+
364
+ def inputs_merger(
365
+ self,
366
+ input_ids: torch.LongTensor,
367
+ inputs_embeds: torch.Tensor,
368
+ image_hidden_states: torch.Tensor,
369
+ ):
370
+ _, patch_size, _ = image_hidden_states.shape
371
+ image_mask = input_ids == self.config.image_token_id
372
+ num_image_tokens = image_mask.sum(dim=1)
373
+
374
+ if not torch.all(num_image_tokens % patch_size == 0):
375
+ raise ValueError(
376
+ "At least one sample has <image> tokens not divisible by patch_size."
377
+ )
378
+
379
+ blocks_per_sample = num_image_tokens // patch_size
380
+ offsets = torch.nn.functional.pad(
381
+ blocks_per_sample.cumsum(dim=0), (1, 0), value=0
382
+ )
383
+ block_offset = offsets[:-1]
384
+ row_cum = image_mask.cumsum(dim=-1)
385
+ chunk_idx = (row_cum - 1) // patch_size
386
+ local_idx = (row_cum - 1) % patch_size
387
+ block_idx = block_offset.unsqueeze(1) + chunk_idx
388
+
389
+ image_embeds = torch.zeros_like(inputs_embeds)
390
+ image_embeds[image_mask] = image_hidden_states[
391
+ block_idx[image_mask], local_idx[image_mask], :
392
+ ]
393
+
394
+ merged_embeds = torch.where(
395
+ image_mask.unsqueeze(-1), image_embeds, inputs_embeds
396
+ )
397
+ return merged_embeds
398
+
399
+ def forward(
400
+ self,
401
+ input_ids: typing.Optional[torch.LongTensor] = None,
402
+ attention_mask: typing.Optional[torch.Tensor] = None,
403
+ position_ids: typing.Optional[torch.LongTensor] = None,
404
+ past_key_values: typing.Optional[list[torch.FloatTensor]] = None,
405
+ inputs_embeds: typing.Optional[torch.FloatTensor] = None,
406
+ pixel_values: typing.Optional[torch.FloatTensor] = None,
407
+ image_hidden_states: typing.Optional[torch.FloatTensor] = None,
408
+ output_attentions: typing.Optional[bool] = None,
409
+ output_hidden_states: typing.Optional[bool] = None,
410
+ use_cache: typing.Optional[bool] = None,
411
+ cache_position: typing.Optional[torch.LongTensor] = None,
412
+ return_dict: typing.Optional[bool] = None,
413
+ **kwargs: Unpack[FlashAttentionKwargs],
414
+ ) -> FastVLMBaseModelOutputWithPast:
415
+ output_attentions = (
416
+ output_attentions
417
+ if output_attentions is not None
418
+ else self.config.output_attentions
419
+ )
420
+ output_hidden_states = (
421
+ output_hidden_states
422
+ if output_hidden_states is not None
423
+ else self.config.output_hidden_states
424
+ )
425
+ use_cache = use_cache if use_cache is not None else self.config.use_cache
426
+ return_dict = (
427
+ return_dict if return_dict is not None else self.config.use_return_dict
428
+ )
429
+
430
+ if self.training and self.text_model.gradient_checkpointing and use_cache:
431
+ logging.warning(
432
+ "`use_cache=True` is incompatible with gradient checkpointing. "
433
+ "Setting `use_cache=False`..."
434
+ )
435
+ use_cache = False
436
+
437
+ # retrieve input_ids and inputs_embeds
438
+ if input_ids is not None:
439
+ _, _ = input_ids.shape
440
+ elif inputs_embeds is not None:
441
+ _, _, _ = inputs_embeds.shape
442
+ else:
443
+ raise ValueError("You have to specify either input_ids or inputs_embeds")
444
+
445
+ past_seen_tokens = 0
446
+ if use_cache:
447
+ if past_key_values is None:
448
+ past_key_values = DynamicCache()
449
+
450
+ past_seen_tokens = (
451
+ past_key_values.get_seq_length() if past_key_values is not None else 0
452
+ )
453
+
454
+ if inputs_embeds is not None and input_ids is None and past_seen_tokens == 0:
455
+ raise ValueError(
456
+ "When first calling the model, if input_embeds are passed, input_ids "
457
+ "should not be None."
458
+ )
459
+
460
+ if inputs_embeds is None:
461
+ inputs_embeds = self.text_model.get_input_embeddings()(input_ids).to(
462
+ input_ids.device
463
+ )
464
+
465
+ # START VISUAL INPUTS INTEGRATION
466
+ if pixel_values is not None and image_hidden_states is not None:
467
+ raise ValueError(
468
+ "You cannot specify both pixel_values and image_hidden_states at the "
469
+ "same time"
470
+ )
471
+ elif pixel_values is not None:
472
+ image_hidden_states = self.get_image_features(pixel_values).to(
473
+ dtype=self.dtype, device=input_ids.device
474
+ )
475
+ elif image_hidden_states is not None:
476
+ image_hidden_states = image_hidden_states.to(
477
+ dtype=self.dtype, device=input_ids.device
478
+ )
479
+
480
+ if inputs_embeds is not None and image_hidden_states is not None:
481
+ inputs_embeds = self.inputs_merger(
482
+ input_ids=input_ids,
483
+ inputs_embeds=inputs_embeds,
484
+ image_hidden_states=image_hidden_states,
485
+ )
486
+
487
+ outputs = self.text_model(
488
+ inputs_embeds=inputs_embeds,
489
+ attention_mask=attention_mask,
490
+ position_ids=position_ids,
491
+ past_key_values=past_key_values,
492
+ output_attentions=output_attentions,
493
+ output_hidden_states=output_hidden_states,
494
+ use_cache=use_cache,
495
+ cache_position=cache_position,
496
+ **kwargs,
497
+ )
498
+ return FastVLMBaseModelOutputWithPast(
499
+ last_hidden_state=outputs.last_hidden_state,
500
+ past_key_values=outputs.past_key_values,
501
+ hidden_states=outputs.hidden_states,
502
+ attentions=outputs.attentions,
503
+ image_hidden_states=image_hidden_states,
504
+ )
505
+
506
+
507
+ class FastViT(torch.nn.Module):
508
+ """The FastViT model. <https://arxiv.org/pdf/2303.14189.pdf>"""
509
+
510
+ def __init__(self, config: FastViTConfig) -> None:
511
+ super().__init__()
512
+ n_stages = len(config.n_blocks)
513
+ if config.position_embeddings is None:
514
+ config.position_embeddings = [None] * n_stages
515
+ if config.downsample_use_se is None:
516
+ config.downsample_use_se = [False] * n_stages
517
+
518
+ # Stem
519
+ self.patch_embedding = torch.nn.Sequential(
520
+ MobileOneBlock(
521
+ in_channels=config.in_channels,
522
+ out_channels=config.embed_dims[0],
523
+ kernel_size=3,
524
+ stride=2,
525
+ padding=1,
526
+ groups=1,
527
+ inference_mode=config.inference_mode,
528
+ use_se=False,
529
+ num_conv_branches=1,
530
+ use_scale_branch=config.use_scale_branch,
531
+ ),
532
+ MobileOneBlock(
533
+ in_channels=config.embed_dims[0],
534
+ out_channels=config.embed_dims[0],
535
+ kernel_size=3,
536
+ stride=2,
537
+ padding=1,
538
+ groups=config.embed_dims[0],
539
+ inference_mode=config.inference_mode,
540
+ use_se=False,
541
+ num_conv_branches=1,
542
+ use_scale_branch=config.use_scale_branch,
543
+ ),
544
+ MobileOneBlock(
545
+ in_channels=config.embed_dims[0],
546
+ out_channels=config.embed_dims[0],
547
+ kernel_size=1,
548
+ stride=1,
549
+ padding=0,
550
+ groups=1,
551
+ inference_mode=config.inference_mode,
552
+ use_se=False,
553
+ num_conv_branches=1,
554
+ use_scale_branch=config.use_scale_branch,
555
+ ),
556
+ )
557
+ # Stage Blocks
558
+ stages = []
559
+ for i in range(n_stages):
560
+ # Add position embeddings as requested
561
+ pe = self._get_position_embedding(config.position_embeddings[i])
562
+ if pe is not None:
563
+ stages.append(
564
+ pe(
565
+ config.embed_dims[i],
566
+ config.embed_dims[i],
567
+ inference_mode=config.inference_mode,
568
+ )
569
+ )
570
+
571
+ stage = self._get_stage_blocks(
572
+ config.embed_dims[i],
573
+ i,
574
+ config.n_blocks,
575
+ token_mixer_type=config.token_mixers[i],
576
+ kernel_size=config.repmixer_kernel_size,
577
+ mlp_ratio=config.mlp_ratios[i],
578
+ norm_layer=LayerNormChannel,
579
+ drop_path_rate=config.drop_path_rate,
580
+ use_layer_scale=config.use_layer_scale,
581
+ layer_scale_init_value=config.layer_scale_init_value,
582
+ inference_mode=config.inference_mode,
583
+ )
584
+ stages.append(stage)
585
+ # Patch merging/downsampling between stages
586
+ if i == n_stages - 1:
587
+ break
588
+ if config.downsample[i] or config.embed_dims[i] != config.embed_dims[i + 1]:
589
+ stages.append(
590
+ PatchEmbedding(
591
+ patch_size=config.downsample_patch_size,
592
+ stride=config.downsample_stride,
593
+ in_channels=config.embed_dims[i],
594
+ embed_dim=config.embed_dims[i + 1],
595
+ inference_mode=config.inference_mode,
596
+ use_se=config.downsample_use_se[i + 1],
597
+ )
598
+ )
599
+
600
+ self.layers = torch.nn.ModuleList(stages)
601
+ self.conv_exp = MobileOneBlock(
602
+ in_channels=config.embed_dims[-1],
603
+ out_channels=config.embed_dims[-1] * config.clsss_ratio,
604
+ kernel_size=3,
605
+ stride=1,
606
+ padding=1,
607
+ groups=config.embed_dims[-1],
608
+ inference_mode=config.inference_mode,
609
+ use_se=True,
610
+ num_conv_branches=1,
611
+ )
612
+ self.apply(self._init_weights)
613
+
614
+ def _init_weights(self, m: torch.nn.Module) -> None:
615
+ """Init. for classification"""
616
+ if isinstance(m, torch.nn.Linear):
617
+ torch.nn.init.normal_(m.weight, std=0.02)
618
+ if isinstance(m, torch.nn.Linear) and m.bias is not None:
619
+ torch.nn.init.constant_(m.bias, 0)
620
+
621
+ def _get_position_embedding(self, config):
622
+ if config is None:
623
+ return None
624
+ elif config["name"] == "RepCPE":
625
+ return functools.partial(RepCPE, spatial_shape=config["spatial_shape"])
626
+ else:
627
+ raise ValueError(f"Position embedding {config['name']} not supported.")
628
+
629
+ def _get_stage_blocks(
630
+ self,
631
+ dim: int,
632
+ block_index: int,
633
+ num_blocks: int,
634
+ token_mixer_type: str,
635
+ kernel_size: int = 3,
636
+ mlp_ratio: float = 4.0,
637
+ act_layer: torch.nn.Module = torch.nn.GELU,
638
+ norm_layer: torch.nn.Module = torch.nn.BatchNorm2d,
639
+ drop_rate: float = 0.0,
640
+ drop_path_rate: float = 0.0,
641
+ use_layer_scale: bool = True,
642
+ layer_scale_init_value: float = 1e-5,
643
+ inference_mode=False,
644
+ ) -> torch.nn.Sequential:
645
+ blocks = []
646
+ for block_idx in range(num_blocks[block_index]):
647
+ block_dpr = (
648
+ drop_path_rate
649
+ * (block_idx + sum(num_blocks[:block_index]))
650
+ / (sum(num_blocks) - 1)
651
+ )
652
+ if token_mixer_type == "repmixer":
653
+ blocks.append(
654
+ RepMixerBlock(
655
+ dim,
656
+ kernel_size=kernel_size,
657
+ mlp_ratio=mlp_ratio,
658
+ act_layer=act_layer,
659
+ drop=drop_rate,
660
+ drop_path=block_dpr,
661
+ use_layer_scale=use_layer_scale,
662
+ layer_scale_init_value=layer_scale_init_value,
663
+ inference_mode=inference_mode,
664
+ )
665
+ )
666
+ elif token_mixer_type == "attention":
667
+ blocks.append(
668
+ AttentionBlock(
669
+ dim,
670
+ mlp_ratio=mlp_ratio,
671
+ act_layer=act_layer,
672
+ norm_layer=norm_layer,
673
+ drop=drop_rate,
674
+ drop_path=block_dpr,
675
+ use_layer_scale=use_layer_scale,
676
+ layer_scale_init_value=layer_scale_init_value,
677
+ )
678
+ )
679
+ else:
680
+ raise ValueError(
681
+ "Token mixer type: {} not supported".format(token_mixer_type)
682
+ )
683
+
684
+ return torch.nn.Sequential(*blocks)
685
+
686
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
687
+ x = self.patch_embedding(x)
688
+ for layer in self.layers:
689
+ x = layer(x)
690
+
691
+ return self.conv_exp(x)
692
+
693
+
694
+ class FastVLMConnector(torch.nn.Module):
695
+ def __init__(self, config: FastVLMConfig) -> None:
696
+ super().__init__()
697
+
698
+ vision_out_dim = (
699
+ config.vision_config.embed_dims[-1] * config.vision_config.clsss_ratio
700
+ )
701
+ llm_hidden_size = config.text_config.hidden_size
702
+ self.connector = torch.nn.Sequential(
703
+ torch.nn.Linear(vision_out_dim, llm_hidden_size),
704
+ torch.nn.GELU(),
705
+ torch.nn.Linear(llm_hidden_size, llm_hidden_size),
706
+ )
707
+
708
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
709
+ return self.connector(x)
710
+
711
+
712
+ # NOTE: The following components are copied from
713
+ # https://huggingface.co/apple/FastVLM-0.5B/blob/main/llava_qwen.py
714
+ #
715
+ # Licensed under the Apache License, Version 2.0 (the "License");
716
+ # you may not use this file except in compliance with the License.
717
+ # You may obtain a copy of the License at
718
+ #
719
+ # http://www.apache.org/licenses/LICENSE-2.0
720
+ #
721
+ # Unless required by applicable law or agreed to in writing, software
722
+ # distributed under the License is distributed on an "AS IS" BASIS,
723
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
724
+ # See the License for the specific language governing permissions and
725
+ # limitations under the License.
726
+
727
+
728
+ class RepCPE(torch.nn.Module):
729
+ """Conditional Positional Encodings for Vision Transformers <https://arxiv.org/pdf/2102.10882.pdf>"""
730
+
731
+ def __init__(
732
+ self,
733
+ in_channels: int,
734
+ embed_dim: int = 768,
735
+ spatial_shape: typing.Union[int, typing.Tuple[int, int]] = (7, 7),
736
+ inference_mode=False,
737
+ ) -> None:
738
+ super(RepCPE, self).__init__()
739
+ if isinstance(spatial_shape, int):
740
+ spatial_shape = tuple([spatial_shape] * 2)
741
+ assert isinstance(spatial_shape, typing.Tuple), (
742
+ f'"spatial_shape" must by a sequence or int, '
743
+ f"get {type(spatial_shape)} instead."
744
+ )
745
+ assert len(spatial_shape) == 2, (
746
+ f'Length of "spatial_shape" should be 2, '
747
+ f"got {len(spatial_shape)} instead."
748
+ )
749
+
750
+ self.spatial_shape = spatial_shape
751
+ self.embed_dim = embed_dim
752
+ self.in_channels = in_channels
753
+ self.groups = embed_dim
754
+
755
+ if inference_mode:
756
+ self.reparam_conv = torch.nn.Conv2d(
757
+ in_channels=self.in_channels,
758
+ out_channels=self.embed_dim,
759
+ kernel_size=self.spatial_shape,
760
+ stride=1,
761
+ padding=int(self.spatial_shape[0] // 2),
762
+ groups=self.embed_dim,
763
+ bias=True,
764
+ )
765
+ else:
766
+ self.pe = torch.nn.Conv2d(
767
+ in_channels,
768
+ embed_dim,
769
+ spatial_shape,
770
+ 1,
771
+ int(spatial_shape[0] // 2),
772
+ bias=True,
773
+ groups=embed_dim,
774
+ )
775
+
776
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
777
+ if hasattr(self, "reparam_conv"):
778
+ x = self.reparam_conv(x)
779
+ return x
780
+ else:
781
+ x = self.pe(x) + x
782
+ return x
783
+
784
+ def reparameterize(self) -> None:
785
+ # Build equivalent Id tensor
786
+ input_dim = self.in_channels // self.groups
787
+ kernel_value = torch.zeros(
788
+ (
789
+ self.in_channels,
790
+ input_dim,
791
+ self.spatial_shape[0],
792
+ self.spatial_shape[1],
793
+ ),
794
+ dtype=self.pe.weight.dtype,
795
+ device=self.pe.weight.device,
796
+ )
797
+ for i in range(self.in_channels):
798
+ kernel_value[
799
+ i,
800
+ i % input_dim,
801
+ self.spatial_shape[0] // 2,
802
+ self.spatial_shape[1] // 2,
803
+ ] = 1
804
+ id_tensor = kernel_value
805
+
806
+ # Reparameterize Id tensor and conv
807
+ w_final = id_tensor + self.pe.weight
808
+ b_final = self.pe.bias
809
+
810
+ # Introduce reparam conv
811
+ self.reparam_conv = torch.nn.Conv2d(
812
+ in_channels=self.in_channels,
813
+ out_channels=self.embed_dim,
814
+ kernel_size=self.spatial_shape,
815
+ stride=1,
816
+ padding=int(self.spatial_shape[0] // 2),
817
+ groups=self.embed_dim,
818
+ bias=True,
819
+ )
820
+ self.reparam_conv.weight.data = w_final
821
+ self.reparam_conv.bias.data = b_final
822
+
823
+ self.__delattr__("pe")
824
+
825
+
826
+ class LayerNormChannel(torch.nn.Module):
827
+ """LayerNorm only for Channel Dimension"""
828
+
829
+ def __init__(self, num_features, eps=1e-05) -> None:
830
+ super().__init__()
831
+ self.weight = torch.nn.Parameter(torch.ones(num_features))
832
+ self.bias = torch.nn.Parameter(torch.zeros(num_features))
833
+ self.eps = eps
834
+
835
+ def forward(self, x) -> torch.Tensor:
836
+ u = x.mean(1, keepdim=True)
837
+ s = (x - u).pow(2).mean(1, keepdim=True)
838
+ x = (x - u) / torch.sqrt(s + self.eps)
839
+ x = self.weight.unsqueeze(-1).unsqueeze(-1) * x + self.bias.unsqueeze(
840
+ -1
841
+ ).unsqueeze(-1)
842
+
843
+ return x
844
+
845
+
846
+ class MobileOneBlock(torch.nn.Module):
847
+ """The MobileOne building block. <https://arxiv.org/pdf/2206.04040.pdf>"""
848
+
849
+ def __init__(
850
+ self,
851
+ in_channels: int,
852
+ out_channels: int,
853
+ kernel_size: int,
854
+ stride: int = 1,
855
+ padding: int = 0,
856
+ dilation: int = 1,
857
+ groups: int = 1,
858
+ inference_mode: bool = False,
859
+ use_se: bool = False,
860
+ use_act: bool = True,
861
+ use_scale_branch: bool = True,
862
+ num_conv_branches: int = 1,
863
+ activation: torch.nn.Module = torch.nn.GELU(),
864
+ ) -> None:
865
+ super(MobileOneBlock, self).__init__()
866
+ self.inference_mode = inference_mode
867
+ self.groups = groups
868
+ self.stride = stride
869
+ self.padding = padding
870
+ self.dilation = dilation
871
+ self.kernel_size = kernel_size
872
+ self.in_channels = in_channels
873
+ self.out_channels = out_channels
874
+ self.num_conv_branches = num_conv_branches
875
+
876
+ # Check if SE-ReLU is requested
877
+ if use_se:
878
+ self.se = SEBlock(out_channels)
879
+ else:
880
+ self.se = torch.nn.Identity()
881
+
882
+ if use_act:
883
+ self.activation = activation
884
+ else:
885
+ self.activation = torch.nn.Identity()
886
+
887
+ if inference_mode:
888
+ self.reparam_conv = torch.nn.Conv2d(
889
+ in_channels=in_channels,
890
+ out_channels=out_channels,
891
+ kernel_size=kernel_size,
892
+ stride=stride,
893
+ padding=padding,
894
+ dilation=dilation,
895
+ groups=groups,
896
+ bias=True,
897
+ )
898
+ else:
899
+ # Re-parameterizable skip connection
900
+ # Fallback, sometimes batchnorm tensors
901
+ # do not get instantiated correctly on some processes
902
+ # when using deepspeed + accelerate
903
+ norm_layer = torch.nn.BatchNorm2d(num_features=in_channels)
904
+ if norm_layer.weight.shape[0] == 0:
905
+ norm_layer.weight = torch.nn.Parameter(torch.zeros(in_channels))
906
+ if norm_layer.bias.shape[0] == 0:
907
+ norm_layer.bias = torch.nn.Parameter(torch.zeros(in_channels))
908
+
909
+ self.rbr_skip = (
910
+ norm_layer if out_channels == in_channels and stride == 1 else None
911
+ )
912
+
913
+ # Re-parameterizable conv branches
914
+ if num_conv_branches > 0:
915
+ rbr_conv = list()
916
+ for _ in range(self.num_conv_branches):
917
+ rbr_conv.append(
918
+ self._conv_bn(kernel_size=kernel_size, padding=padding)
919
+ )
920
+ self.rbr_conv = torch.nn.ModuleList(rbr_conv)
921
+ else:
922
+ self.rbr_conv = None
923
+
924
+ # Re-parameterizable scale branch
925
+ self.rbr_scale = None
926
+ if not isinstance(kernel_size, int):
927
+ kernel_size = kernel_size[0]
928
+ if (kernel_size > 1) and use_scale_branch:
929
+ self.rbr_scale = self._conv_bn(kernel_size=1, padding=0)
930
+
931
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
932
+ """Apply forward pass."""
933
+ # Inference mode forward pass.
934
+ if self.inference_mode:
935
+ return self.activation(self.se(self.reparam_conv(x)))
936
+
937
+ # Multi-branched train-time forward pass.
938
+ # Skip branch output
939
+ identity_out = 0
940
+ if self.rbr_skip is not None:
941
+ identity_out = self.rbr_skip(x)
942
+
943
+ # Scale branch output
944
+ scale_out = 0
945
+ if self.rbr_scale is not None:
946
+ scale_out = self.rbr_scale(x)
947
+
948
+ # Other branches
949
+ out = scale_out + identity_out
950
+ if self.rbr_conv is not None:
951
+ for ix in range(self.num_conv_branches):
952
+ out += self.rbr_conv[ix](x)
953
+
954
+ return self.activation(self.se(out))
955
+
956
+ def reparameterize(self):
957
+ """Following works like `RepVGG: Making VGG-style ConvNets Great Again` -
958
+ https://arxiv.org/pdf/2101.03697.pdf. We re-parameterize multi-branched
959
+ architecture used at training time to obtain a plain CNN-like structure
960
+ for inference.
961
+ """
962
+ if self.inference_mode:
963
+ return
964
+
965
+ kernel, bias = self._get_kernel_bias()
966
+ self.reparam_conv = torch.nn.Conv2d(
967
+ in_channels=self.in_channels,
968
+ out_channels=self.out_channels,
969
+ kernel_size=self.kernel_size,
970
+ stride=self.stride,
971
+ padding=self.padding,
972
+ dilation=self.dilation,
973
+ groups=self.groups,
974
+ bias=True,
975
+ )
976
+ self.reparam_conv.weight.data = kernel
977
+ self.reparam_conv.bias.data = bias
978
+
979
+ # Delete un-used branches
980
+ self.__delattr__("rbr_conv")
981
+ self.__delattr__("rbr_scale")
982
+ if hasattr(self, "rbr_skip"):
983
+ self.__delattr__("rbr_skip")
984
+
985
+ self.inference_mode = True
986
+
987
+ def _get_kernel_bias(self) -> typing.Tuple[torch.Tensor, torch.Tensor]:
988
+ """Method to obtain re-parameterized kernel and bias.
989
+ Reference: https://github.com/DingXiaoH/RepVGG/blob/main/repvgg.py#L83
990
+ """
991
+ # get weights and bias of scale branch
992
+ kernel_scale = 0
993
+ bias_scale = 0
994
+ if self.rbr_scale is not None:
995
+ kernel_scale, bias_scale = self._fuse_bn_tensor(self.rbr_scale)
996
+ # Pad scale branch kernel to match conv branch kernel size.
997
+ pad = self.kernel_size // 2
998
+ kernel_scale = torch.nn.functional.pad(kernel_scale, [pad, pad, pad, pad])
999
+
1000
+ # get weights and bias of skip branch
1001
+ kernel_identity = 0
1002
+ bias_identity = 0
1003
+ if self.rbr_skip is not None:
1004
+ kernel_identity, bias_identity = self._fuse_bn_tensor(self.rbr_skip)
1005
+
1006
+ # get weights and bias of conv branches
1007
+ kernel_conv = 0
1008
+ bias_conv = 0
1009
+ if self.rbr_conv is not None:
1010
+ for ix in range(self.num_conv_branches):
1011
+ _kernel, _bias = self._fuse_bn_tensor(self.rbr_conv[ix])
1012
+ kernel_conv += _kernel
1013
+ bias_conv += _bias
1014
+
1015
+ kernel_final = kernel_conv + kernel_scale + kernel_identity
1016
+ bias_final = bias_conv + bias_scale + bias_identity
1017
+ return kernel_final, bias_final
1018
+
1019
+ def _fuse_bn_tensor(
1020
+ self, branch: typing.Union[torch.nn.Sequential, torch.nn.BatchNorm2d]
1021
+ ) -> typing.Tuple[torch.Tensor, torch.Tensor]:
1022
+ """Method to fuse batchnorm layer with preceeding conv layer.
1023
+ Reference: https://github.com/DingXiaoH/RepVGG/blob/main/repvgg.py#L95
1024
+ """
1025
+ if isinstance(branch, torch.nn.Sequential):
1026
+ kernel = branch.conv.weight
1027
+ running_mean = branch.bn.running_mean
1028
+ running_var = branch.bn.running_var
1029
+ gamma = branch.bn.weight
1030
+ beta = branch.bn.bias
1031
+ eps = branch.bn.eps
1032
+ else:
1033
+ assert isinstance(branch, torch.nn.BatchNorm2d)
1034
+ if not hasattr(self, "id_tensor"):
1035
+ input_dim = self.in_channels // self.groups
1036
+
1037
+ kernel_size = self.kernel_size
1038
+ if isinstance(self.kernel_size, int):
1039
+ kernel_size = (self.kernel_size, self.kernel_size)
1040
+
1041
+ kernel_value = torch.zeros(
1042
+ (self.in_channels, input_dim, kernel_size[0], kernel_size[1]),
1043
+ dtype=branch.weight.dtype,
1044
+ device=branch.weight.device,
1045
+ )
1046
+ for i in range(self.in_channels):
1047
+ kernel_value[
1048
+ i, i % input_dim, kernel_size[0] // 2, kernel_size[1] // 2
1049
+ ] = 1
1050
+ self.id_tensor = kernel_value
1051
+ kernel = self.id_tensor
1052
+ running_mean = branch.running_mean
1053
+ running_var = branch.running_var
1054
+ gamma = branch.weight
1055
+ beta = branch.bias
1056
+ eps = branch.eps
1057
+ std = (running_var + eps).sqrt()
1058
+ t = (gamma / std).reshape(-1, 1, 1, 1)
1059
+ return kernel * t, beta - running_mean * gamma / std
1060
+
1061
+ def _conv_bn(self, kernel_size: int, padding: int) -> torch.nn.Sequential:
1062
+ # Fallback, sometimes batchnorm tensors
1063
+ # do not get instantiated correctly on some processes
1064
+ # when using deepspeed + accelerate
1065
+ norm_layer = torch.nn.BatchNorm2d(num_features=self.out_channels)
1066
+ if norm_layer.weight.shape[0] == 0:
1067
+ norm_layer.weight = torch.nn.Parameter(torch.zeros(self.out_channels))
1068
+ if norm_layer.bias.shape[0] == 0:
1069
+ norm_layer.bias = torch.nn.Parameter(torch.zeros(self.out_channels))
1070
+
1071
+ mod_list = torch.nn.Sequential()
1072
+ mod_list.add_module(
1073
+ "conv",
1074
+ torch.nn.Conv2d(
1075
+ in_channels=self.in_channels,
1076
+ out_channels=self.out_channels,
1077
+ kernel_size=kernel_size,
1078
+ stride=self.stride,
1079
+ padding=padding,
1080
+ groups=self.groups,
1081
+ bias=False,
1082
+ ),
1083
+ )
1084
+ mod_list.add_module("bn", norm_layer)
1085
+ return mod_list
1086
+
1087
+
1088
+ class SEBlock(torch.nn.Module):
1089
+ """The Squeeze and Excite module. <https://arxiv.org/pdf/1709.01507.pdf>"""
1090
+
1091
+ def __init__(self, in_channels: int, rd_ratio: float = 0.0625) -> None:
1092
+ super(SEBlock, self).__init__()
1093
+ self.reduce = torch.nn.Conv2d(
1094
+ in_channels=in_channels,
1095
+ out_channels=int(in_channels * rd_ratio),
1096
+ kernel_size=1,
1097
+ stride=1,
1098
+ bias=True,
1099
+ )
1100
+ self.expand = torch.nn.Conv2d(
1101
+ in_channels=int(in_channels * rd_ratio),
1102
+ out_channels=in_channels,
1103
+ kernel_size=1,
1104
+ stride=1,
1105
+ bias=True,
1106
+ )
1107
+
1108
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
1109
+ _, c, h, w = inputs.size()
1110
+ x = F.avg_pool2d(inputs, kernel_size=[h, w])
1111
+ # x = F.avg_pool2d(inputs, kernel_size=[16, 16])
1112
+ x = self.reduce(x)
1113
+ x = F.relu(x)
1114
+ x = self.expand(x)
1115
+ x = torch.sigmoid(x)
1116
+ x = x.view(-1, c, 1, 1)
1117
+ return inputs * x
1118
+
1119
+
1120
+ class RepMixerBlock(torch.nn.Module):
1121
+ """The Metaformer block with RepMixer as token mixer <https://arxiv.org/pdf/2111.11418.pdf>"""
1122
+
1123
+ def __init__(
1124
+ self,
1125
+ dim: int,
1126
+ kernel_size: int = 3,
1127
+ mlp_ratio: float = 4.0,
1128
+ act_layer: torch.nn.Module = torch.nn.GELU,
1129
+ drop: float = 0.0,
1130
+ drop_path: float = 0.0,
1131
+ use_layer_scale: bool = True,
1132
+ layer_scale_init_value: float = 1e-5,
1133
+ inference_mode: bool = False,
1134
+ ):
1135
+ super().__init__()
1136
+
1137
+ self.token_mixer = RepMixer(
1138
+ dim,
1139
+ kernel_size=kernel_size,
1140
+ use_layer_scale=use_layer_scale,
1141
+ layer_scale_init_value=layer_scale_init_value,
1142
+ inference_mode=inference_mode,
1143
+ )
1144
+ assert mlp_ratio > 0, "MLP ratio should be greater than 0, found: {}".format(
1145
+ mlp_ratio
1146
+ )
1147
+ mlp_hidden_dim = int(dim * mlp_ratio)
1148
+ self.convffn = ConvFFN(
1149
+ in_channels=dim,
1150
+ hidden_channels=mlp_hidden_dim,
1151
+ act_layer=act_layer,
1152
+ drop=drop,
1153
+ )
1154
+ # Drop Path
1155
+ self.drop_path = DropPath(drop_path) if drop_path > 0.0 else torch.nn.Identity()
1156
+ # Layer Scale
1157
+ self.use_layer_scale = use_layer_scale
1158
+ if use_layer_scale:
1159
+ self.layer_scale = torch.nn.Parameter(
1160
+ layer_scale_init_value * torch.ones((dim, 1, 1)), requires_grad=True
1161
+ )
1162
+
1163
+ def forward(self, x):
1164
+ if self.use_layer_scale:
1165
+ x = self.token_mixer(x)
1166
+ x = x + self.drop_path(self.layer_scale * self.convffn(x))
1167
+ else:
1168
+ x = self.token_mixer(x)
1169
+ x = x + self.drop_path(self.convffn(x))
1170
+ return x
1171
+
1172
+
1173
+ class RepMixer(torch.nn.Module):
1174
+ """Reparameterizable token mixer. <https://arxiv.org/pdf/2303.14189.pdf>"""
1175
+
1176
+ def __init__(
1177
+ self,
1178
+ dim,
1179
+ kernel_size=3,
1180
+ use_layer_scale=True,
1181
+ layer_scale_init_value=1e-5,
1182
+ inference_mode: bool = False,
1183
+ ):
1184
+ super().__init__()
1185
+ self.dim = dim
1186
+ self.kernel_size = kernel_size
1187
+ self.inference_mode = inference_mode
1188
+
1189
+ if inference_mode:
1190
+ self.reparam_conv = torch.nn.Conv2d(
1191
+ in_channels=self.dim,
1192
+ out_channels=self.dim,
1193
+ kernel_size=self.kernel_size,
1194
+ stride=1,
1195
+ padding=self.kernel_size // 2,
1196
+ groups=self.dim,
1197
+ bias=True,
1198
+ )
1199
+ else:
1200
+ self.norm = MobileOneBlock(
1201
+ dim,
1202
+ dim,
1203
+ kernel_size,
1204
+ padding=kernel_size // 2,
1205
+ groups=dim,
1206
+ use_act=False,
1207
+ use_scale_branch=False,
1208
+ num_conv_branches=0,
1209
+ )
1210
+ self.mixer = MobileOneBlock(
1211
+ dim,
1212
+ dim,
1213
+ kernel_size,
1214
+ padding=kernel_size // 2,
1215
+ groups=dim,
1216
+ use_act=False,
1217
+ )
1218
+ self.use_layer_scale = use_layer_scale
1219
+ if use_layer_scale:
1220
+ self.layer_scale = torch.nn.Parameter(
1221
+ layer_scale_init_value * torch.ones((dim, 1, 1)), requires_grad=True
1222
+ )
1223
+
1224
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1225
+ if hasattr(self, "reparam_conv"):
1226
+ x = self.reparam_conv(x)
1227
+ return x
1228
+ else:
1229
+ if self.use_layer_scale:
1230
+ x = x + self.layer_scale * (self.mixer(x) - self.norm(x))
1231
+ else:
1232
+ x = x + self.mixer(x) - self.norm(x)
1233
+ return x
1234
+
1235
+ def reparameterize(self) -> None:
1236
+ if self.inference_mode:
1237
+ return
1238
+
1239
+ self.mixer.reparameterize()
1240
+ self.norm.reparameterize()
1241
+
1242
+ if self.use_layer_scale:
1243
+ w = self.mixer.id_tensor + self.layer_scale.unsqueeze(-1) * (
1244
+ self.mixer.reparam_conv.weight - self.norm.reparam_conv.weight
1245
+ )
1246
+ b = torch.squeeze(self.layer_scale) * (
1247
+ self.mixer.reparam_conv.bias - self.norm.reparam_conv.bias
1248
+ )
1249
+ else:
1250
+ w = (
1251
+ self.mixer.id_tensor
1252
+ + self.mixer.reparam_conv.weight
1253
+ - self.norm.reparam_conv.weight
1254
+ )
1255
+ b = self.mixer.reparam_conv.bias - self.norm.reparam_conv.bias
1256
+
1257
+ self.reparam_conv = torch.nn.Conv2d(
1258
+ in_channels=self.dim,
1259
+ out_channels=self.dim,
1260
+ kernel_size=self.kernel_size,
1261
+ stride=1,
1262
+ padding=self.kernel_size // 2,
1263
+ groups=self.dim,
1264
+ bias=True,
1265
+ )
1266
+ self.reparam_conv.weight.data = w
1267
+ self.reparam_conv.bias.data = b
1268
+
1269
+ self.__delattr__("mixer")
1270
+ self.__delattr__("norm")
1271
+ if self.use_layer_scale:
1272
+ self.__delattr__("layer_scale")
1273
+
1274
+
1275
+ class ConvFFN(torch.nn.Module):
1276
+ """Convolutional FFN Module."""
1277
+
1278
+ def __init__(
1279
+ self,
1280
+ in_channels: int,
1281
+ hidden_channels: typing.Optional[int] = None,
1282
+ out_channels: typing.Optional[int] = None,
1283
+ act_layer: torch.nn.Module = torch.nn.GELU,
1284
+ drop: float = 0.0,
1285
+ ) -> None:
1286
+ super().__init__()
1287
+ out_channels = out_channels or in_channels
1288
+ hidden_channels = hidden_channels or in_channels
1289
+ self.conv = torch.nn.Sequential()
1290
+ self.conv.add_module(
1291
+ "conv",
1292
+ torch.nn.Conv2d(
1293
+ in_channels=in_channels,
1294
+ out_channels=out_channels,
1295
+ kernel_size=7,
1296
+ padding=3,
1297
+ groups=in_channels,
1298
+ bias=False,
1299
+ ),
1300
+ )
1301
+
1302
+ # Fallback, sometimes batchnorm tensors
1303
+ # do not get instantiated correctly on some processes
1304
+ # when using deepspeed + accelerate
1305
+ norm_layer = torch.nn.BatchNorm2d(num_features=out_channels)
1306
+ if norm_layer.weight.shape[0] == 0:
1307
+ norm_layer.weight = torch.nn.Parameter(torch.zeros(out_channels))
1308
+ if norm_layer.bias.shape[0] == 0:
1309
+ norm_layer.bias = torch.nn.Parameter(torch.zeros(out_channels))
1310
+
1311
+ self.conv.add_module("bn", norm_layer)
1312
+ self.fc1 = torch.nn.Conv2d(in_channels, hidden_channels, kernel_size=1)
1313
+ self.act = act_layer()
1314
+ self.fc2 = torch.nn.Conv2d(hidden_channels, out_channels, kernel_size=1)
1315
+ self.drop = torch.nn.Dropout(drop)
1316
+ self.apply(self._init_weights)
1317
+
1318
+ def _init_weights(self, m: torch.nn.Module) -> None:
1319
+ if isinstance(m, torch.nn.Conv2d):
1320
+ torch.nn.init.normal_(m.weight, std=0.02)
1321
+ if m.bias is not None:
1322
+ torch.nn.init.constant_(m.bias, 0)
1323
+
1324
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1325
+ x = self.conv(x)
1326
+ x = self.fc1(x)
1327
+ x = self.act(x)
1328
+ x = self.drop(x)
1329
+ x = self.fc2(x)
1330
+ x = self.drop(x)
1331
+ return x
1332
+
1333
+
1334
+ class AttentionBlock(torch.nn.Module):
1335
+ """The metaformer block with MHSA as token mixer. <https://arxiv.org/pdf/2111.11418.pdf>"""
1336
+
1337
+ def __init__(
1338
+ self,
1339
+ dim: int,
1340
+ mlp_ratio: float = 4.0,
1341
+ act_layer: torch.nn.Module = torch.nn.GELU,
1342
+ norm_layer: torch.nn.Module = torch.nn.BatchNorm2d,
1343
+ drop: float = 0.0,
1344
+ drop_path: float = 0.0,
1345
+ use_layer_scale: bool = True,
1346
+ layer_scale_init_value: float = 1e-5,
1347
+ ):
1348
+ super().__init__()
1349
+
1350
+ # Fallback, sometimes batchnorm tensors
1351
+ # do not get instantiated correctly on some processes
1352
+ # when using deepspeed + accelerate
1353
+ norm_layer_ = norm_layer(num_features=dim)
1354
+ if norm_layer_.weight.shape[0] == 0:
1355
+ norm_layer_.weight = torch.nn.Parameter(torch.zeros(dim))
1356
+ if norm_layer_.bias.shape[0] == 0:
1357
+ norm_layer_.bias = torch.nn.Parameter(torch.zeros(dim))
1358
+
1359
+ self.norm = norm_layer_
1360
+ self.token_mixer = MHSA(dim=dim)
1361
+ assert mlp_ratio > 0, "MLP ratio should be greater than 0, found: {}".format(
1362
+ mlp_ratio
1363
+ )
1364
+ mlp_hidden_dim = int(dim * mlp_ratio)
1365
+ self.convffn = ConvFFN(
1366
+ in_channels=dim,
1367
+ hidden_channels=mlp_hidden_dim,
1368
+ act_layer=act_layer,
1369
+ drop=drop,
1370
+ )
1371
+ # Drop path
1372
+ self.drop_path = DropPath(drop_path) if drop_path > 0.0 else torch.nn.Identity()
1373
+ # Layer Scale
1374
+ self.use_layer_scale = use_layer_scale
1375
+ if use_layer_scale:
1376
+ self.layer_scale_1 = torch.nn.Parameter(
1377
+ layer_scale_init_value * torch.ones((dim, 1, 1)), requires_grad=True
1378
+ )
1379
+ self.layer_scale_2 = torch.nn.Parameter(
1380
+ layer_scale_init_value * torch.ones((dim, 1, 1)), requires_grad=True
1381
+ )
1382
+
1383
+ def forward(self, x):
1384
+ if self.use_layer_scale:
1385
+ x = x + self.drop_path(self.layer_scale_1 * self.token_mixer(self.norm(x)))
1386
+ x = x + self.drop_path(self.layer_scale_2 * self.convffn(x))
1387
+ else:
1388
+ x = x + self.drop_path(self.token_mixer(self.norm(x)))
1389
+ x = x + self.drop_path(self.convffn(x))
1390
+ return x
1391
+
1392
+
1393
+ class MHSA(torch.nn.Module):
1394
+ """Multi-headed Self Attention module. Source modified from:
1395
+ https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py
1396
+ """
1397
+
1398
+ def __init__(
1399
+ self,
1400
+ dim: int,
1401
+ head_dim: int = 32,
1402
+ qkv_bias: bool = False,
1403
+ attn_drop: float = 0.0,
1404
+ proj_drop: float = 0.0,
1405
+ ) -> None:
1406
+ super().__init__()
1407
+ assert dim % head_dim == 0, "dim should be divisible by head_dim"
1408
+ self.head_dim = head_dim
1409
+ self.num_heads = dim // head_dim
1410
+ self.scale = head_dim**-0.5
1411
+
1412
+ self.qkv = torch.nn.Linear(dim, dim * 3, bias=qkv_bias)
1413
+ self.attn_drop = torch.nn.Dropout(attn_drop)
1414
+ self.proj = torch.nn.Linear(dim, dim)
1415
+ self.proj_drop = torch.nn.Dropout(proj_drop)
1416
+
1417
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1418
+ shape = x.shape
1419
+ B, C, H, W = shape
1420
+ N = H * W
1421
+ if len(shape) == 4:
1422
+ x = torch.flatten(x, start_dim=2).transpose(-2, -1) # (B, N, C)
1423
+ qkv = (
1424
+ self.qkv(x)
1425
+ .reshape(B, N, 3, self.num_heads, self.head_dim)
1426
+ .permute(2, 0, 3, 1, 4)
1427
+ )
1428
+ q, k, v = qkv.unbind(0) # make torchscript happy (cannot use tensor as tuple)
1429
+
1430
+ # trick here to make q@k.t more stable
1431
+ attn = (q * self.scale) @ k.transpose(-2, -1)
1432
+ attn = attn.softmax(dim=-1)
1433
+ attn = self.attn_drop(attn)
1434
+
1435
+ x = (attn @ v).transpose(1, 2).reshape(B, N, C)
1436
+ x = self.proj(x)
1437
+ x = self.proj_drop(x)
1438
+ if len(shape) == 4:
1439
+ x = x.transpose(-2, -1).reshape(B, C, H, W)
1440
+
1441
+ return x
1442
+
1443
+
1444
+ class PatchEmbedding(torch.nn.Module):
1445
+ """Convolutional patch embedding layer."""
1446
+
1447
+ def __init__(
1448
+ self,
1449
+ patch_size: int,
1450
+ stride: int,
1451
+ in_channels: int,
1452
+ embed_dim: int,
1453
+ inference_mode: bool = False,
1454
+ use_se: bool = False,
1455
+ ) -> None:
1456
+ super().__init__()
1457
+ block = list()
1458
+ block.append(
1459
+ ReparamLargeKernelConv(
1460
+ in_channels=in_channels,
1461
+ out_channels=embed_dim,
1462
+ kernel_size=patch_size,
1463
+ stride=stride,
1464
+ groups=in_channels,
1465
+ small_kernel=3,
1466
+ inference_mode=inference_mode,
1467
+ use_se=use_se,
1468
+ )
1469
+ )
1470
+ block.append(
1471
+ MobileOneBlock(
1472
+ in_channels=embed_dim,
1473
+ out_channels=embed_dim,
1474
+ kernel_size=1,
1475
+ stride=1,
1476
+ padding=0,
1477
+ groups=1,
1478
+ inference_mode=inference_mode,
1479
+ use_se=False,
1480
+ num_conv_branches=1,
1481
+ )
1482
+ )
1483
+ self.proj = torch.nn.Sequential(*block)
1484
+
1485
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1486
+ x = self.proj(x)
1487
+ return x
1488
+
1489
+
1490
+ class ReparamLargeKernelConv(torch.nn.Module):
1491
+ """The Building Block of RepLKNet <https://arxiv.org/abs/2203.06717>"""
1492
+
1493
+ def __init__(
1494
+ self,
1495
+ in_channels: int,
1496
+ out_channels: int,
1497
+ kernel_size: int,
1498
+ stride: int,
1499
+ groups: int,
1500
+ small_kernel: int,
1501
+ inference_mode: bool = False,
1502
+ use_se: bool = False,
1503
+ activation: torch.nn.Module = torch.nn.GELU(),
1504
+ ) -> None:
1505
+ super(ReparamLargeKernelConv, self).__init__()
1506
+
1507
+ self.stride = stride
1508
+ self.groups = groups
1509
+ self.in_channels = in_channels
1510
+ self.out_channels = out_channels
1511
+ self.activation = activation
1512
+
1513
+ self.kernel_size = kernel_size
1514
+ self.small_kernel = small_kernel
1515
+ self.padding = kernel_size // 2
1516
+
1517
+ # Check if SE is requested
1518
+ if use_se:
1519
+ self.se = SqueezeExcite(out_channels, rd_ratio=0.25)
1520
+ else:
1521
+ self.se = torch.nn.Identity()
1522
+
1523
+ if inference_mode:
1524
+ self.lkb_reparam = torch.nn.Conv2d(
1525
+ in_channels=in_channels,
1526
+ out_channels=out_channels,
1527
+ kernel_size=kernel_size,
1528
+ stride=stride,
1529
+ padding=self.padding,
1530
+ dilation=1,
1531
+ groups=groups,
1532
+ bias=True,
1533
+ )
1534
+ else:
1535
+ self.lkb_origin = self._conv_bn(
1536
+ kernel_size=kernel_size, padding=self.padding
1537
+ )
1538
+ if small_kernel is not None:
1539
+ assert (
1540
+ small_kernel <= kernel_size
1541
+ ), "The kernel size for re-param cannot be larger than the large kernel!"
1542
+ self.small_conv = self._conv_bn(
1543
+ kernel_size=small_kernel, padding=small_kernel // 2
1544
+ )
1545
+
1546
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
1547
+ """Apply forward pass."""
1548
+ if hasattr(self, "lkb_reparam"):
1549
+ out = self.lkb_reparam(x)
1550
+ else:
1551
+ out = self.lkb_origin(x)
1552
+ if hasattr(self, "small_conv"):
1553
+ out += self.small_conv(x)
1554
+
1555
+ return self.activation(self.se(out))
1556
+
1557
+ def get_kernel_bias(self) -> typing.Tuple[torch.Tensor, torch.Tensor]:
1558
+ """Method to obtain re-parameterized kernel and bias.
1559
+ Reference: https://github.com/DingXiaoH/RepLKNet-pytorch
1560
+ Returns:
1561
+ Tuple of (kernel, bias) after fusing branches.
1562
+ """
1563
+ eq_k, eq_b = self._fuse_bn(self.lkb_origin.conv, self.lkb_origin.bn)
1564
+ if hasattr(self, "small_conv"):
1565
+ small_k, small_b = self._fuse_bn(self.small_conv.conv, self.small_conv.bn)
1566
+ eq_b += small_b
1567
+ eq_k += torch.nn.functional.pad(
1568
+ small_k, [(self.kernel_size - self.small_kernel) // 2] * 4
1569
+ )
1570
+ return eq_k, eq_b
1571
+
1572
+ def reparameterize(self) -> None:
1573
+ """
1574
+ Following works like `RepVGG: Making VGG-style ConvNets Great Again` -
1575
+ https://arxiv.org/pdf/2101.03697.pdf. We re-parameterize multi-branched
1576
+ architecture used at training time to obtain a plain CNN-like structure
1577
+ for inference.
1578
+ """
1579
+ eq_k, eq_b = self.get_kernel_bias()
1580
+ self.lkb_reparam = torch.nn.Conv2d(
1581
+ in_channels=self.in_channels,
1582
+ out_channels=self.out_channels,
1583
+ kernel_size=self.kernel_size,
1584
+ stride=self.stride,
1585
+ padding=self.padding,
1586
+ dilation=self.lkb_origin.conv.dilation,
1587
+ groups=self.groups,
1588
+ bias=True,
1589
+ )
1590
+
1591
+ self.lkb_reparam.weight.data = eq_k
1592
+ self.lkb_reparam.bias.data = eq_b
1593
+ self.__delattr__("lkb_origin")
1594
+ if hasattr(self, "small_conv"):
1595
+ self.__delattr__("small_conv")
1596
+
1597
+ @staticmethod
1598
+ def _fuse_bn(
1599
+ conv: torch.Tensor, bn: torch.nn.BatchNorm2d
1600
+ ) -> typing.Tuple[torch.Tensor, torch.Tensor]:
1601
+ kernel = conv.weight
1602
+ running_mean = bn.running_mean
1603
+ running_var = bn.running_var
1604
+ gamma = bn.weight
1605
+ beta = bn.bias
1606
+ eps = bn.eps
1607
+ std = (running_var + eps).sqrt()
1608
+ t = (gamma / std).reshape(-1, 1, 1, 1)
1609
+ return kernel * t, beta - running_mean * gamma / std
1610
+
1611
+ def _conv_bn(self, kernel_size: int, padding: int = 0) -> torch.nn.Sequential:
1612
+ # Fallback, sometimes batchnorm tensors
1613
+ # do not get instantiated correctly on some processes
1614
+ # when using deepspeed + accelerate
1615
+ norm_layer = torch.nn.BatchNorm2d(num_features=self.out_channels)
1616
+ if norm_layer.weight.shape[0] == 0:
1617
+ norm_layer.weight = torch.nn.Parameter(torch.zeros(self.out_channels))
1618
+ if norm_layer.bias.shape[0] == 0:
1619
+ norm_layer.bias = torch.nn.Parameter(torch.zeros(self.out_channels))
1620
+
1621
+ mod_list = torch.nn.Sequential()
1622
+ mod_list.add_module(
1623
+ "conv",
1624
+ torch.nn.Conv2d(
1625
+ in_channels=self.in_channels,
1626
+ out_channels=self.out_channels,
1627
+ kernel_size=kernel_size,
1628
+ stride=self.stride,
1629
+ padding=padding,
1630
+ groups=self.groups,
1631
+ bias=False,
1632
+ ),
1633
+ )
1634
+ mod_list.add_module("bn", norm_layer)
1635
+ return mod_list
policies/dynamicvla/modeling_vlm_with_expert.py ADDED
@@ -0,0 +1,658 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # @File: modeling_vlm_with_expert.py
4
+ # @Author: Haozhe Xie
5
+ # @Date: 2025-09-16 11:23:15
6
+ # @Last Modified by: Haozhe Xie
7
+ # @Last Modified at: 2026-02-26 11:29:07
8
+ # @Email: root@haozhexie.com
9
+
10
+ import collections
11
+ import copy
12
+ import logging
13
+
14
+ import torch
15
+ from transformers import AutoModel, AutoTokenizer, PretrainedConfig, PreTrainedModel
16
+
17
+
18
+ class VLMWithExpertModel(torch.nn.Module):
19
+ def __init__(
20
+ self,
21
+ model_id: str,
22
+ vlm: PreTrainedModel,
23
+ freeze_text_model: bool = False,
24
+ freeze_connector: bool = False,
25
+ freeze_vision_model: bool = False,
26
+ attention_mode: str = "self_attn",
27
+ num_expert_layers: int = -1,
28
+ num_expert_skip_layers: int = 0,
29
+ num_vlm_layers: int = -1,
30
+ self_attn_every_n_layers: int = -1,
31
+ expert_width_multiplier: float = 0.5,
32
+ ) -> None:
33
+ super().__init__()
34
+ # Tokenizer
35
+ self.tokenizer = AutoTokenizer.from_pretrained(model_id)
36
+ if self.tokenizer.pad_token is None:
37
+ self.tokenizer.pad_token = self.tokenizer.eos_token
38
+ # VLM
39
+ self.vlm = vlm
40
+ self.vlm_config = self.vlm.config
41
+ if hasattr(self.vlm, "lm_head"):
42
+ del self.vlm.lm_head
43
+ if num_vlm_layers > 0:
44
+ logging.info(
45
+ "Reducing the number of VLM layers from %d to %d ..."
46
+ % (
47
+ len(self._get_text_model(self.get_vlm_model()).layers),
48
+ num_vlm_layers,
49
+ )
50
+ )
51
+ del self._get_text_model(self.get_vlm_model()).layers[num_vlm_layers:]
52
+
53
+ self.num_vlm_layers = len(self._get_text_model(self.get_vlm_model()).layers)
54
+ # Action Expert
55
+ lm_expert_config = self._get_expert_config(
56
+ self.vlm_config.text_config, num_expert_layers, expert_width_multiplier
57
+ )
58
+ self.lm_expert = self._get_expert_model(
59
+ lm_expert_config,
60
+ attention_mode,
61
+ num_expert_skip_layers,
62
+ self_attn_every_n_layers,
63
+ )
64
+ # Remove token embeddings
65
+ if hasattr(self.lm_expert, "embed_tokens"):
66
+ del self.lm_expert.embed_tokens
67
+
68
+ self.num_attention_heads = self.vlm_config.text_config.num_attention_heads
69
+ self.num_key_value_heads = self.vlm_config.text_config.num_key_value_heads
70
+ self.num_vlm_layers = len(self._get_text_model(self.get_vlm_model()).layers)
71
+ self.num_expert_layers = len(self.lm_expert.layers) - num_expert_skip_layers
72
+ self.num_expert_skip_layers = num_expert_skip_layers
73
+ self.self_attn_every_n_layers = self_attn_every_n_layers
74
+
75
+ self.freeze_vision_model = freeze_vision_model
76
+ self.freeze_connector = freeze_connector
77
+ self.freeze_text_model = freeze_text_model
78
+ self.attention_mode = attention_mode
79
+ self.expert_hidden_size = lm_expert_config.hidden_size
80
+ self._set_requires_grad()
81
+
82
+ def _get_vision_model(self, vlm):
83
+ if hasattr(vlm, "vision_model"):
84
+ return vlm.vision_model
85
+ else:
86
+ raise ValueError("Cannot find vision model in the VLM")
87
+
88
+ def _get_text_model(self, vlm):
89
+ if hasattr(vlm, "text_model"):
90
+ return vlm.text_model
91
+ else:
92
+ raise ValueError("Cannot find text model in the VLM")
93
+
94
+ def _get_expert_config(
95
+ self, text_config: PretrainedConfig, num_layers: int, width_multiplier: float
96
+ ) -> PretrainedConfig:
97
+ expert_config = copy.deepcopy(text_config)
98
+ hidden_size = expert_config.hidden_size
99
+ expert_config.hidden_size = int(hidden_size * width_multiplier)
100
+ expert_config.intermediate_size = self._get_intermediate_size(
101
+ expert_config.hidden_size
102
+ )
103
+ num_vlm_layers = len(self._get_text_model(self.get_vlm_model()).layers)
104
+ expert_config.num_hidden_layers = num_vlm_layers
105
+ if num_layers > 0:
106
+ assert (
107
+ num_vlm_layers % num_layers == 0
108
+ ), "Number of layers in the VLM %d are not multiple of %d " % (
109
+ num_vlm_layers,
110
+ num_layers,
111
+ )
112
+ expert_config.num_hidden_layers = num_layers
113
+
114
+ return expert_config
115
+
116
+ def _get_intermediate_size(
117
+ self, hidden_dim: int, ffn_dim_multiplier: int = 4, multiple_of: int = 256
118
+ ) -> int:
119
+ hidden_dim = int(2 * hidden_dim / 3)
120
+ hidden_dim = int(ffn_dim_multiplier * hidden_dim)
121
+ hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)
122
+ return hidden_dim
123
+
124
+ def _get_expert_model(
125
+ self,
126
+ expert_config: PretrainedConfig,
127
+ attention_mode: str,
128
+ num_expert_skip_layers: int,
129
+ self_attn_every_n_layers: int,
130
+ ) -> PreTrainedModel:
131
+ text_config = self.vlm_config.text_config
132
+ if not hasattr(text_config, "head_dim"):
133
+ text_config.head_dim = (
134
+ text_config.hidden_size // text_config.num_attention_heads
135
+ )
136
+ expert_config.head_dim = text_config.head_dim
137
+ if not hasattr(expert_config, "attention_bias"):
138
+ expert_config.attention_bias = False
139
+
140
+ expert_model = AutoModel.from_config(expert_config)
141
+ if "cross" in attention_mode:
142
+ # Reshape qkv projections to have the same input dimension as the vlm
143
+ for layer_idx in range(num_expert_skip_layers, len(expert_model.layers)):
144
+ if (
145
+ self_attn_every_n_layers > 0
146
+ and layer_idx % self_attn_every_n_layers == 0
147
+ ):
148
+ continue
149
+
150
+ # Remove projectors for key (as ROPE has been applied in VLM)
151
+ expert_model.layers[layer_idx].self_attn.v_proj = torch.nn.Linear(
152
+ text_config.num_key_value_heads * text_config.head_dim,
153
+ expert_config.num_key_value_heads * expert_config.head_dim,
154
+ bias=expert_config.attention_bias,
155
+ )
156
+ del expert_model.layers[layer_idx].self_attn.k_proj
157
+
158
+ return expert_model
159
+
160
+ def get_vlm_model(self) -> PreTrainedModel:
161
+ return self.vlm.model
162
+
163
+ def _set_requires_grad(self) -> None:
164
+ if self.freeze_vision_model:
165
+ self._get_vision_model(self.get_vlm_model()).eval()
166
+ for params in self._get_vision_model(self.get_vlm_model()).parameters():
167
+ params.requires_grad = False
168
+ if self.freeze_text_model:
169
+ self._get_text_model(self.get_vlm_model()).eval()
170
+ for params in self._get_text_model(self.get_vlm_model()).parameters():
171
+ params.requires_grad = False
172
+ if self.freeze_connector and hasattr(self.get_vlm_model(), "connector"):
173
+ self.get_vlm_model().connector.eval()
174
+ for params in self.get_vlm_model().connector.parameters():
175
+ params.requires_grad = False
176
+
177
+ def train(self, mode: bool = True) -> None:
178
+ super().train(mode)
179
+ if self.freeze_vision_model:
180
+ self._get_vision_model(self.get_vlm_model()).eval()
181
+ if self.freeze_connector and hasattr(self.get_vlm_model(), "connector"):
182
+ self.get_vlm_model().connector.eval()
183
+ if self.freeze_text_model:
184
+ self._get_text_model(self.get_vlm_model()).eval()
185
+
186
+ def embed_image(self, image: torch.Tensor) -> torch.Tensor:
187
+ assert len(image.shape) in [
188
+ 4,
189
+ 5,
190
+ ], f"Image should be [B, C, H, W] or [B, N, C, H, W], got {image.shape}"
191
+ if len(image.shape) == 4:
192
+ image = image.unsqueeze(1) # [B, 1, C, H, W]
193
+
194
+ return self.get_vlm_model().get_image_features(image)
195
+
196
+ def embed_language_tokens(self, tokens: torch.Tensor) -> torch.Tensor:
197
+ return self._get_text_model(self.get_vlm_model()).get_input_embeddings()(tokens)
198
+
199
+ def _qkv_proj_layer(
200
+ self,
201
+ layer: torch.nn.Module,
202
+ position_ids: torch.Tensor,
203
+ q_in: torch.Tensor,
204
+ k_in: torch.Tensor | None = None,
205
+ v_in: torch.Tensor | None = None,
206
+ ) -> tuple[
207
+ dict[int, torch.Tensor], dict[int, torch.Tensor], dict[int, torch.Tensor]
208
+ ]:
209
+ assert q_in is not None, "q_in should not be None"
210
+
211
+ attn_layer = layer.self_attn
212
+ q_in = layer.input_layernorm(q_in).to(dtype=attn_layer.q_proj.weight.dtype)
213
+ if k_in is None and v_in is None:
214
+ k_in = q_in
215
+ v_in = q_in
216
+ else:
217
+ v_in = v_in.to(dtype=attn_layer.v_proj.weight.dtype)
218
+ if hasattr(attn_layer, "k_proj"):
219
+ k_in = k_in.to(dtype=attn_layer.k_proj.weight.dtype)
220
+
221
+ v_shape = (*v_in.shape[:-1], -1, attn_layer.head_dim)
222
+ q_shape = (*q_in.shape[:-1], -1, attn_layer.head_dim)
223
+ if hasattr(attn_layer, "k_proj"):
224
+ k_shape = (*k_in.shape[:-1], -1, attn_layer.head_dim)
225
+
226
+ q_states, k_states, v_states = {}, {}, {}
227
+ if position_ids.ndim == 2: # 1D Rope
228
+ v_states["t"] = attn_layer.v_proj(v_in).view(v_shape)
229
+ q_states["t"] = attn_layer.q_proj(q_in).view(q_shape)
230
+ if hasattr(attn_layer, "q_norm"):
231
+ q_states["t"] = attn_layer.q_norm(q_states["t"])
232
+ if hasattr(attn_layer, "k_proj"):
233
+ k_states["t"] = attn_layer.k_proj(k_in).view(k_shape)
234
+ if hasattr(attn_layer, "k_norm"):
235
+ k_states["t"] = attn_layer.k_norm(k_states["t"])
236
+ elif position_ids.ndim == 3 and position_ids.shape[2] == 3: # 3D Rope
237
+ qtr_head_dim = attn_layer.head_dim // 4
238
+ # Value
239
+ v_states["t"] = attn_layer.v_proj(v_in).view(v_shape)
240
+ # Query
241
+ _query_states = attn_layer.q_norm(attn_layer.q_proj(q_in).view(q_shape))
242
+ q_states["t"] = _query_states[..., : qtr_head_dim * 2]
243
+ q_states["h"] = _query_states[..., qtr_head_dim * 2 : -qtr_head_dim]
244
+ q_states["w"] = _query_states[..., -qtr_head_dim:]
245
+ # Key (can be skipped in cross-attention)
246
+ if hasattr(attn_layer, "k_proj"):
247
+ _key_states = attn_layer.k_norm(attn_layer.k_proj(k_in).view(k_shape))
248
+ k_states["t"] = _key_states[..., : qtr_head_dim * 2]
249
+ k_states["h"] = _key_states[..., qtr_head_dim * 2 : -qtr_head_dim]
250
+ k_states["w"] = _key_states[..., -qtr_head_dim:]
251
+ else:
252
+ raise ValueError(f"Unknown position_ids shape: {position_ids.shape}")
253
+
254
+ return q_states, k_states, v_states
255
+
256
+ def apply_rope(
257
+ self,
258
+ hidden_states: torch.Tensor,
259
+ positions: torch.Tensor,
260
+ wavelength: int = 10_000,
261
+ ):
262
+ if positions.ndim == 2: # 1D Rope
263
+ return self._apply_rope(
264
+ hidden_states["t"].unsqueeze(0), positions.unsqueeze(0), wavelength
265
+ ).squeeze(0)
266
+ elif positions.ndim == 3 and positions.shape[2] == 3: # 3D Rope
267
+ roped_states = self._apply_rope(
268
+ torch.stack(
269
+ [
270
+ hidden_states["t"],
271
+ hidden_states["h"].repeat_interleave(2, dim=-1),
272
+ hidden_states["w"].repeat_interleave(2, dim=-1),
273
+ ],
274
+ dim=0,
275
+ ),
276
+ positions.permute(2, 0, 1),
277
+ wavelength,
278
+ )
279
+ half_indexes = torch.arange(
280
+ 0, hidden_states["t"].size(-1), 2, device=roped_states.device
281
+ )
282
+ roped_states = roped_states.permute(1, 2, 3, 4, 0)
283
+ return torch.cat(
284
+ [
285
+ roped_states[..., 0],
286
+ roped_states[..., half_indexes, 1],
287
+ roped_states[..., half_indexes, 2],
288
+ ],
289
+ dim=-1,
290
+ )
291
+ else:
292
+ raise ValueError(f"Unknown position ID shape: {positions.shape}")
293
+
294
+ def _apply_rope(
295
+ self,
296
+ hidden_states: torch.Tensor,
297
+ positions: torch.Tensor,
298
+ max_wavelength: int = 10_000,
299
+ ):
300
+ """
301
+ Applies RoPE positions [B, L, N] to hidden_states [B, L, H, D].
302
+ """
303
+ # Cache the sin/cos values for efficiency
304
+ d_half = hidden_states.size(-1) // 2
305
+ dtype = hidden_states.dtype
306
+ x = hidden_states.to(torch.float32)
307
+
308
+ freq_exponents = (4.0 / d_half) * torch.arange(
309
+ d_half, dtype=torch.float32, device=positions.device
310
+ )
311
+ timescale = max_wavelength**freq_exponents
312
+ radians = positions[..., None] / timescale[None, None, None, :]
313
+ radians = radians[..., None, :]
314
+ cos = torch.cos(radians)
315
+ sin = torch.sin(radians)
316
+
317
+ x1, x2 = hidden_states.split(d_half, dim=-1)
318
+ res = torch.empty_like(x)
319
+ res[..., :d_half] = x1 * cos - x2 * sin
320
+ res[..., d_half:] = x2 * cos + x1 * sin
321
+ return res.to(dtype)
322
+
323
+ def _self_attn_layer(
324
+ self,
325
+ model_layer: list[torch.nn.Module],
326
+ inputs_embeds: list[torch.Tensor],
327
+ position_ids: torch.Tensor,
328
+ attention_mask: torch.Tensor,
329
+ head_dim: int,
330
+ use_cache: bool = True,
331
+ fill_kv_cache: bool = True,
332
+ past_key_values: dict[str, torch.Tensor] | None = None,
333
+ ) -> list[torch.Tensor]:
334
+ query_states = collections.defaultdict(list)
335
+ key_states = collections.defaultdict(list)
336
+ value_states = collections.defaultdict(list)
337
+ for i, hidden_states in enumerate(inputs_embeds):
338
+ layer = model_layer[i]
339
+ if hidden_states is None or layer is None:
340
+ continue
341
+
342
+ q_states, k_states, v_states = self._qkv_proj_layer(
343
+ layer, position_ids, hidden_states
344
+ )
345
+ for dst, src in (
346
+ (query_states, q_states),
347
+ (key_states, k_states),
348
+ (value_states, v_states),
349
+ ):
350
+ for k, v in src.items():
351
+ dst[k].append(v)
352
+
353
+ # B, L, H, D with L sequence length, H number of heads, D head dim
354
+ # Concatenate on the number of embeddings/tokens
355
+ for states in (query_states, key_states, value_states):
356
+ for k, v_list in states.items():
357
+ states[k] = torch.cat(v_list, dim=1)
358
+
359
+ # Both VLM and Expert are empty. May happen during inference.
360
+ if len(query_states["t"]) == 0:
361
+ return None, past_key_values
362
+
363
+ seq_len = query_states["t"].shape[1]
364
+ if seq_len < position_ids.shape[1]:
365
+ _position_ids = position_ids[:, :seq_len]
366
+ _attention_mask = attention_mask[:, :seq_len, :seq_len]
367
+ else:
368
+ _position_ids = position_ids
369
+ _attention_mask = attention_mask
370
+
371
+ # Rotary Position Embedding
372
+ value_states = value_states["t"]
373
+ query_states = self.apply_rope(query_states, _position_ids)
374
+ key_states = self.apply_rope(key_states, _position_ids)
375
+
376
+ # KV Cache
377
+ if use_cache and past_key_values is not None:
378
+ if fill_kv_cache:
379
+ past_key_values = {
380
+ "key_states": key_states,
381
+ "value_states": value_states,
382
+ }
383
+ else: # TODO: some optimization can be done, similar to `StaticCache`
384
+ key_states = torch.cat(
385
+ [past_key_values["key_states"], key_states], dim=1
386
+ )
387
+ value_states = torch.cat(
388
+ [past_key_values["value_states"], value_states], dim=1
389
+ )
390
+
391
+ # Eager Attention
392
+ att_output = self._eager_attention(
393
+ head_dim,
394
+ query_states,
395
+ key_states,
396
+ value_states,
397
+ _attention_mask,
398
+ )
399
+ return [att_output], past_key_values
400
+
401
+ def _cross_attn_layer(
402
+ self,
403
+ model_layer: list[torch.nn.Module],
404
+ inputs_embeds: list[torch.Tensor],
405
+ position_ids: torch.Tensor,
406
+ attention_mask: torch.Tensor,
407
+ head_dim: int,
408
+ use_cache: bool = True,
409
+ fill_kv_cache: bool = True,
410
+ past_key_values: dict[str, torch.Tensor] | None = None,
411
+ ) -> list[torch.Tensor]:
412
+ assert len(inputs_embeds) == 2 or (
413
+ use_cache and past_key_values is not None and not fill_kv_cache
414
+ )
415
+
416
+ att_outputs = []
417
+ # VLM
418
+ if len(inputs_embeds) == 2 and not past_key_values:
419
+ # Prefix attention
420
+ seq_len = inputs_embeds[0].shape[1]
421
+ prefix_position_ids, suffix_position_ids = (
422
+ position_ids[:, :seq_len],
423
+ position_ids[:, seq_len:],
424
+ )
425
+ _query_states, _key_states, value_states = self._qkv_proj_layer(
426
+ model_layer[0], prefix_position_ids, inputs_embeds[0]
427
+ )
428
+ # Rotary Position Embedding
429
+ value_states = value_states["t"]
430
+ query_states = self.apply_rope(_query_states, prefix_position_ids)
431
+ key_states = self.apply_rope(_key_states, prefix_position_ids)
432
+ # Eager Attention
433
+ prefix_attention_mask = attention_mask[:, :seq_len, :seq_len]
434
+ att_output = self._eager_attention(
435
+ head_dim,
436
+ query_states,
437
+ key_states,
438
+ value_states,
439
+ prefix_attention_mask,
440
+ )
441
+ att_outputs.append(att_output)
442
+ else:
443
+ suffix_position_ids = position_ids
444
+
445
+ if use_cache and past_key_values is not None:
446
+ if fill_kv_cache:
447
+ past_key_values = {
448
+ "key_states": key_states,
449
+ "value_states": value_states,
450
+ }
451
+ else: # TODO: some optimization can be done, similar to `StaticCache`
452
+ key_states = past_key_values["key_states"]
453
+ value_states = past_key_values["value_states"]
454
+
455
+ # Expert
456
+ expert_layer = model_layer[1]
457
+ if expert_layer is not None:
458
+ # NOTE: key_states has been ROPEd before. Directly use it here.
459
+ _query_states, _, _value_states = self._qkv_proj_layer(
460
+ expert_layer,
461
+ suffix_position_ids,
462
+ inputs_embeds[1],
463
+ None,
464
+ value_states.view(*value_states.shape[:2], -1),
465
+ )
466
+ suffix_position_ids = (
467
+ suffix_position_ids
468
+ - torch.min(suffix_position_ids, dim=1, keepdim=True).values
469
+ ) # start from 0
470
+ # Rotary Position Embedding
471
+ query_states = self.apply_rope(_query_states, suffix_position_ids)
472
+ value_states = _value_states["t"]
473
+ # Eager Attention
474
+ suffix_attention_mask = attention_mask[
475
+ :, -inputs_embeds[1].shape[1] :, : key_states.shape[1] :
476
+ ]
477
+ att_output = self._eager_attention(
478
+ head_dim,
479
+ query_states,
480
+ key_states,
481
+ value_states,
482
+ suffix_attention_mask,
483
+ )
484
+ att_outputs.append(att_output)
485
+ else:
486
+ att_outputs.append(None)
487
+
488
+ return att_outputs, past_key_values
489
+
490
+ def _get_model_layers(
491
+ self, models: list[PreTrainedModel]
492
+ ) -> list[list[torch.nn.Module]]:
493
+ vlm_layers = []
494
+ expert_layers = []
495
+ for i in range(self.num_expert_skip_layers):
496
+ vlm_layers.append(models[0].layers[i])
497
+ expert_layers.append(None)
498
+
499
+ multiple_of = (
500
+ self.num_vlm_layers - self.num_expert_skip_layers
501
+ ) // self.num_expert_layers
502
+ for i in range(self.num_expert_skip_layers, self.num_vlm_layers):
503
+ if multiple_of > 0 and i > 0 and i % multiple_of != 0:
504
+ expert_layer = None
505
+ else:
506
+ expert_layer_index = i // multiple_of if multiple_of > 0 else i
507
+ expert_layer = models[1].layers[expert_layer_index]
508
+
509
+ vlm_layers.append(models[0].layers[i])
510
+ expert_layers.append(expert_layer)
511
+
512
+ assert len(vlm_layers) == len(expert_layers)
513
+ return [(vlm_layers[i], expert_layers[i]) for i in range(len(vlm_layers))]
514
+
515
+ def forward(
516
+ self,
517
+ attention_mask: torch.Tensor | None = None,
518
+ position_ids: torch.LongTensor | None = None,
519
+ past_key_values: dict[int, dict[str, torch.FloatTensor]] | None = None,
520
+ inputs_embeds: list[torch.FloatTensor] = None,
521
+ use_cache: bool | None = None,
522
+ fill_kv_cache: bool | None = None,
523
+ ) -> tuple[list[torch.FloatTensor], list[torch.FloatTensor] | None]:
524
+ models = [self._get_text_model(self.get_vlm_model()), self.lm_expert]
525
+ model_layers = self._get_model_layers(models)
526
+
527
+ # Decoder Layers
528
+ past_key_values = (
529
+ {i: {} for i in range(self.num_vlm_layers)}
530
+ if use_cache and past_key_values is None
531
+ else past_key_values
532
+ )
533
+ for layer_idx in range(self.num_vlm_layers):
534
+ attn_layer = None
535
+ if (
536
+ fill_kv_cache
537
+ or "cross" not in self.attention_mode
538
+ or layer_idx < self.num_expert_skip_layers
539
+ or (
540
+ self.self_attn_every_n_layers > 0
541
+ and layer_idx % self.self_attn_every_n_layers == 0
542
+ )
543
+ ):
544
+ attn_layer = self._self_attn_layer
545
+ else:
546
+ attn_layer = self._cross_attn_layer
547
+
548
+ start = 0
549
+ outputs_embeds = []
550
+ att_outputs, _past_key_values = attn_layer(
551
+ model_layers[layer_idx],
552
+ inputs_embeds,
553
+ position_ids,
554
+ attention_mask,
555
+ self.vlm.config.text_config.head_dim,
556
+ use_cache,
557
+ fill_kv_cache,
558
+ past_key_values[layer_idx] if past_key_values is not None else None,
559
+ )
560
+ if att_outputs is None:
561
+ continue
562
+ if past_key_values is not None:
563
+ past_key_values[layer_idx] = _past_key_values
564
+
565
+ for i, hidden_states in enumerate(inputs_embeds):
566
+ layer = model_layers[layer_idx][i]
567
+ att_output = (
568
+ att_outputs[i] if i < len(att_outputs) else att_outputs[0]
569
+ ) # in case of self_attn
570
+ if hidden_states is not None:
571
+ if layer is None:
572
+ outputs_embeds.append(hidden_states)
573
+ continue
574
+
575
+ end = start + hidden_states.shape[1]
576
+ if att_output.dtype != layer.self_attn.o_proj.weight.dtype:
577
+ att_output = att_output.to(layer.self_attn.o_proj.weight.dtype)
578
+
579
+ att_out = att_output[:, start:end]
580
+ out_emb = layer.self_attn.o_proj(att_out)
581
+
582
+ out_emb += hidden_states
583
+ after_first_residual = out_emb.clone()
584
+
585
+ # Fully Connected Layer in Decoder Layer
586
+ out_emb = layer.post_attention_layernorm(out_emb)
587
+ out_emb = layer.mlp(out_emb)
588
+ out_emb += after_first_residual
589
+
590
+ outputs_embeds.append(out_emb)
591
+ start = end if len(att_outputs) == 1 else 0
592
+ else:
593
+ outputs_embeds.append(None)
594
+
595
+ inputs_embeds = outputs_embeds
596
+
597
+ # Final norm
598
+ outputs_embeds = []
599
+ for i, hidden_states in enumerate(inputs_embeds):
600
+ if hidden_states is not None:
601
+ out_emb = models[i].norm(hidden_states)
602
+ outputs_embeds.append(out_emb)
603
+ else:
604
+ outputs_embeds.append(None)
605
+
606
+ return outputs_embeds, past_key_values
607
+
608
+ def _repeat_kv(self, hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
609
+ batch, seq_len, num_key_value_heads, head_dim = hidden_states.shape
610
+ if n_rep == 1:
611
+ return hidden_states
612
+
613
+ hidden_states = hidden_states[:, :, :, None, :].expand(
614
+ batch, seq_len, num_key_value_heads, n_rep, head_dim
615
+ )
616
+ return hidden_states.reshape(
617
+ batch, seq_len, num_key_value_heads * n_rep, head_dim
618
+ )
619
+
620
+ def _eager_attention(
621
+ self,
622
+ head_dim: int,
623
+ query: torch.Tensor,
624
+ key: torch.Tensor,
625
+ value: torch.Tensor,
626
+ attention_mask: torch.Tensor,
627
+ ) -> torch.Tensor:
628
+ num_att_heads = self.num_attention_heads
629
+ num_key_value_heads = self.num_key_value_heads
630
+ num_key_value_groups = num_att_heads // num_key_value_heads
631
+
632
+ batch_size = attention_mask.size(0)
633
+ key_states = self._repeat_kv(key, num_key_value_groups)
634
+ value_states = self._repeat_kv(value, num_key_value_groups)
635
+
636
+ # Attention here is upcasted to float32 to match the original implementation.
637
+ att_weights = (head_dim**-0.5) * torch.matmul(
638
+ query.transpose(1, 2).to(dtype=torch.float32),
639
+ key_states.permute(0, 2, 3, 1).to(dtype=torch.float32),
640
+ )
641
+
642
+ att_weights = att_weights.to(dtype=torch.float32)
643
+ masked_att_weights = torch.where(
644
+ attention_mask[:, None, :, :],
645
+ att_weights,
646
+ torch.finfo(att_weights.dtype).min,
647
+ )
648
+ probs = torch.nn.functional.softmax(masked_att_weights, dim=-1)
649
+ probs = probs.to(dtype=value_states.dtype)
650
+
651
+ att_output = torch.matmul(probs, value_states.permute(0, 2, 1, 3))
652
+
653
+ att_output = att_output.permute(0, 2, 1, 3)
654
+ # we use -1 because sequence length can change
655
+ att_output = att_output.reshape(
656
+ batch_size, -1, num_key_value_heads * num_key_value_groups * head_dim
657
+ )
658
+ return att_output
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Do not list: gradio, spaces, huggingface_hub, torch
2
+ # lerobot==0.3.3 is installed --no-deps in app.py (it pins torch<2.8, illegal on ZeroGPU)
3
+
4
+ accelerate
5
+ draccus==0.10.0
6
+ easydict
7
+ einops
8
+ num2words
9
+ numpy
10
+ opencv-python-headless
11
+ packaging
12
+ pandas
13
+ pillow
14
+ plotly
15
+ safetensors
16
+ scipy
17
+ sentencepiece
18
+ timm
19
+ torchvision
20
+ transformers==5.2.0