BonanDing commited on
Commit
ea5e8ea
·
1 Parent(s): 9923b06

Add DeMemWM step 1-4 dataset scaffold

Browse files
.exp_artifact/dememwm_rebuild_plan.md ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # DeMemWM Rebuild Plan
2
+
3
+ ## Goal
4
+
5
+ Rebuild the DeMemWM idea on the `dememwm` branch under `WorldMem` while keeping the existing WorldMem implementation clean and usable as the baseline.
6
+
7
+ The rebuilt design should move memory selection into the dataset, like the current WorldMem dataset path, and keep the DeMemWM algorithm focused on packing, noise handling, model calls, and target-only loss.
8
+
9
+ ## Current Status
10
+
11
+ - Step 1: copied DeMemWM namespace exists, but `algorithms/worldmem` must remain read-only from this point on. The current working tree has no `algorithms/worldmem` diff.
12
+ - Step 2: `dememwm_base` config and experiment registration exist.
13
+ - Step 3: DeMemWM latent dataset exists and returns `[target][anchor][dynamic][revisit]` with `latents`, `actions`, `poses`, `frame_indices`, `memory_segments`, `memory_masks`, and `image_hw`.
14
+ - Step 4: typed DeMemWM dataset selector exists. `worldmem` backend selection is not implemented yet; do not silently rely on `memory_selection.backend: worldmem` until that backend is added or explicitly rejected.
15
+ - Step 5: not started. The current copied algorithm is not yet compatible with the new latent dataset contract; the known data-to-method integration issues are listed under Step 5 below.
16
+ - Steps 6-10: not started.
17
+
18
+ ## 1. Keep `worldmem` Clean
19
+
20
+ Do not modify `algorithms/worldmem` for DeMemWM-specific behavior. It remains the baseline/reference implementation.
21
+
22
+ Create an independent algorithm namespace:
23
+
24
+ ```text
25
+ algorithms/
26
+ worldmem/
27
+ ...
28
+
29
+ dememwm/
30
+ __init__.py
31
+ df_base.py
32
+ df_video.py
33
+ pose_prediction.py
34
+ models/
35
+ attention.py
36
+ diffusion.py
37
+ dit.py
38
+ vae.py
39
+ ...
40
+ ```
41
+
42
+ Initial implementation step: copy `algorithms/worldmem` to `algorithms/dememwm`, then rebuild DeMemWM inside that copy.
43
+
44
+ ## 2. New Base Config
45
+
46
+ Create:
47
+
48
+ ```text
49
+ configurations/algorithm/dememwm_base.yaml
50
+ ```
51
+
52
+ Do not start with `dememwm_frame_memory_ablation.yaml`. Ablation configs can be added later as separate overrides or dedicated YAML files.
53
+
54
+ Register the new algorithm in `experiments/exp_video.py`:
55
+
56
+ ```python
57
+ dememwm_base = DeMemWMMinecraft
58
+ ```
59
+
60
+ The first explicit target run should use:
61
+
62
+ ```bash
63
+ algorithm=dememwm_base
64
+ dataset=video_minecraft_dememwm_latent
65
+ ```
66
+
67
+ ## 3. Dataset-Side Memory Selection
68
+
69
+ Memory selection happens inside the dataset, not inside the algorithm.
70
+
71
+ Create a DeMemWM latent dataset, for example:
72
+
73
+ ```text
74
+ datasets/video/minecraft_video_dememwm_latent_dataset.py
75
+ ```
76
+
77
+ It should load precomputed VAE latents and append memory frames directly:
78
+
79
+ ```text
80
+ [target][anchor][dynamic][revisit]
81
+ ```
82
+
83
+ Returned batch should include:
84
+
85
+ ```python
86
+ latents
87
+ actions
88
+ poses
89
+ frame_indices
90
+ memory_segments
91
+ memory_masks
92
+ image_hw
93
+ ```
94
+
95
+ Example segment metadata:
96
+
97
+ ```python
98
+ memory_segments = {
99
+ "target": T,
100
+ "anchor": A,
101
+ "dynamic": D,
102
+ "revisit": R,
103
+ }
104
+ ```
105
+
106
+ The dataset should also return masks for padded or unavailable memory slots.
107
+
108
+ ## 4. Optional Dataset Selection Backends
109
+
110
+ Add configurable memory selection:
111
+
112
+ ```yaml
113
+ memory_selection:
114
+ enabled: true
115
+ backend: dememwm # worldmem | dememwm
116
+ max_anchor_frames: 2
117
+ max_dynamic_frames: 4
118
+ max_revisit_frames: 2
119
+ ```
120
+
121
+ ### `worldmem` Backend
122
+
123
+ Use FOV-style memory selection similar to current WorldMem. This backend is useful as a baseline and should append selected memory frames after the target sequence.
124
+
125
+ ### `dememwm` Backend
126
+
127
+ Use typed memory streams:
128
+
129
+ - `anchor`: prefix/context frames, optionally pose-diverse.
130
+ - `dynamic`: recent frames before the target window.
131
+ - `revisit`: older causal frames selected by FOV overlap plus Plucker score plus thresholds.
132
+
133
+ Representative config:
134
+
135
+ ```yaml
136
+ memory_selection:
137
+ fov_overlap_threshold: 0.6
138
+ min_total_selected_coverage: 0.1
139
+ local_context_exclusion_frames: 8
140
+ plucker_weight: 0.1
141
+ anchor_diverse_selection: true
142
+ ```
143
+
144
+ Core invariants:
145
+
146
+ - no future memory;
147
+ - dynamic frames come before the target window;
148
+ - revisit excludes local context;
149
+ - anchor prefers prefix/context frames;
150
+ - padded memory slots are masked.
151
+
152
+ ## 5. Thin DeMemWM Algorithm
153
+
154
+ `algorithms/dememwm/df_video.py` should not select memory.
155
+
156
+ Current status: not started. The copied WorldMem algorithm still expects the old tuple/raw-frame path, so the `algorithm=dememwm_base` and `dataset=video_minecraft_dememwm_latent` pairing is not yet runnable as intended.
157
+
158
+ Its responsibilities:
159
+
160
+ 1. Preprocess the batch.
161
+ 2. Read `memory_segments` and `memory_masks`.
162
+ 3. Split target and memory by segment metadata.
163
+ 4. Zero action conditions for memory frames.
164
+ 5. Assign target noise and cleaner or clean memory noise.
165
+ 6. Call DeMemWM diffusion/DiT with memory metadata.
166
+ 7. Compute loss only on target frames.
167
+
168
+ This keeps the division of responsibility simple:
169
+
170
+ - dataset: chooses and appends memory;
171
+ - algorithm: prepares noise/conditions and computes loss;
172
+ - model: performs memory-aware denoising.
173
+
174
+ Known data-to-method integration issues to fix in this step:
175
+
176
+ - Update `_preprocess_batch` to consume the latent dataset dict instead of unpacking the old 4-tuple.
177
+ - Treat `latents` as already VAE-encoded; do not call the VAE encoder on latent tensors.
178
+ - Split returned sequences by `memory_segments` so appended memory frames are not predicted or scored as target frames.
179
+ - Use `memory_masks` so padded memory slots do not become valid conditioning references.
180
+ - Zero action conditions only for valid memory frames.
181
+ - Use returned `image_hw` for Plucker/ray geometry instead of latent tensor height/width.
182
+ - Remove validation/test algorithm-side memory reselection; validation/test should use dataset-provided memory just like training.
183
+ - Keep target-only loss and target-only sampling updates.
184
+
185
+ ## 6. Isolated DeMemWM Model Changes
186
+
187
+ Modify only the copied model files under `algorithms/dememwm/models`.
188
+
189
+ Required changes:
190
+
191
+ ```text
192
+ algorithms/dememwm/models/diffusion.py
193
+ algorithms/dememwm/models/attention.py
194
+ algorithms/dememwm/models/dit.py
195
+ ```
196
+
197
+ ### `diffusion.py`
198
+
199
+ - Accept `memory_segments`, `memory_masks`, and memory pose metadata.
200
+ - Return predictions only for the target segment.
201
+ - Compute training loss only on target frames.
202
+ - During sampling, update only target frames.
203
+
204
+ ### `attention.py`
205
+
206
+ - Add segment-aware temporal masking.
207
+ - Prevent normal temporal self-attention from leaking target/memory information across streams.
208
+ - Preserve ordinary behavior when no memory metadata is provided.
209
+
210
+ ### `dit.py`
211
+
212
+ Add separate reference attention branches:
213
+
214
+ ```text
215
+ r_attn_anchor
216
+ r_attn_dynamic
217
+ r_attn_revisit
218
+ ```
219
+
220
+ The target segment provides queries. Memory streams provide keys and values. Geometry-aware FOV/Plucker information can be used in this path.
221
+
222
+ No DeMemWM-specific changes should be made to `algorithms/worldmem/models/*`.
223
+
224
+ ## 7. Avoid Heavy Algorithm-Side Memory Package
225
+
226
+ Do not port the noisy `algorithms/worldmem/dememwm/*` structure from `~/DeMemWM`.
227
+
228
+ Memory selection should live in the dataset path. If helper logic becomes too large for the dataset file, place it in dataset infrastructure:
229
+
230
+ ```text
231
+ datasets/video/memory_selection.py
232
+ ```
233
+
234
+ Start inside `minecraft_video_dememwm_latent_dataset.py` if possible, then split only if readability requires it.
235
+
236
+ ## 8. Runtime And Checkpoint Infrastructure
237
+
238
+ Merge the useful infrastructure from `~/DeMemWM` into shared runtime files:
239
+
240
+ - safer `load_custom_checkpoint`;
241
+ - compatible prefix matching;
242
+ - shape mismatch reports;
243
+ - strict DeMemWM checkpoint checks;
244
+ - zero new frame-memory gates when loading base WorldMem/Oasis weights;
245
+ - auto-resume;
246
+ - incomplete checkpoint filtering.
247
+
248
+ This belongs in shared `main.py` and `experiments/exp_base.py`, because it benefits both WorldMem and DeMemWM.
249
+
250
+ ## 9. Tests
251
+
252
+ Add tests early.
253
+
254
+ Dataset tests:
255
+
256
+ - dataset appends memory in `[target][anchor][dynamic][revisit]` order;
257
+ - `memory_segments` sums to returned sequence length;
258
+ - masks match padded/unavailable memory slots;
259
+ - `worldmem` selector returns FOV-selected memory;
260
+ - `dememwm` selector returns anchor/dynamic/revisit correctly;
261
+ - causal invariants are enforced.
262
+
263
+ Model/algorithm tests:
264
+
265
+ - registered `algorithm=dememwm_base` + `dataset=video_minecraft_dememwm_latent` smoke path consumes one collated batch;
266
+ - DeMemWM does not VAE-encode precomputed latents;
267
+ - validation/test does not predict or score appended memory frames;
268
+ - Plucker/ray geometry uses `image_hw`, not latent tensor size;
269
+ - padded memory slots are ignored through `memory_masks`;
270
+ - temporal mask blocks cross-stream leakage;
271
+ - diffusion loss only uses target frames;
272
+ - memory action conditions are zeroed;
273
+ - cleaner memory noise is bounded by target noise;
274
+ - DeMemWM checkpoint mismatch reporting catches missing memory adapter keys.
275
+
276
+ ## 10. Launch Scripts Later
277
+
278
+ Do not work on Berzelius launch scripts until the local smoke path works.
279
+
280
+ First milestone:
281
+
282
+ ```bash
283
+ python -m main \
284
+ +name=dememwm_smoke \
285
+ algorithm=dememwm_base \
286
+ dataset=video_minecraft_dememwm_latent
287
+ ```
288
+
289
+ Use a tiny dataset slice and verify one dataloader pass plus one forward/training step before moving to full training scripts.
290
+
algorithms/dememwm/__init__.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Mapping
2
+
3
+ from omegaconf import open_dict
4
+
5
+ from .df_video import WorldMemMinecraft
6
+ from .pose_prediction import PosePrediction
7
+
8
+
9
+ _SEGMENT_KEYS = ("anchor", "dynamic", "revisit")
10
+
11
+
12
+ def _cfg_get(cfg, key: str, default=None):
13
+ if cfg is None:
14
+ return default
15
+ if isinstance(cfg, Mapping):
16
+ return cfg.get(key, default)
17
+ return getattr(cfg, key, default)
18
+
19
+
20
+ def _derive_memory_condition_length(cfg) -> int:
21
+ memory_cfg = _cfg_get(cfg, "memory_selection")
22
+ if memory_cfg is None:
23
+ value = _cfg_get(cfg, "memory_condition_length")
24
+ if value is None:
25
+ raise ValueError("DeMemWM requires memory_selection or memory_condition_length")
26
+ return int(value)
27
+ return sum(int(_cfg_get(memory_cfg, f"max_{key}_frames", 0)) for key in _SEGMENT_KEYS)
28
+
29
+
30
+ class DeMemWMMinecraft(WorldMemMinecraft):
31
+ """DeMemWM namespace wrapper for the copied WorldMem video algorithm."""
32
+
33
+ def __init__(self, cfg):
34
+ if _cfg_get(cfg, "memory_condition_length") is None:
35
+ with open_dict(cfg):
36
+ cfg.memory_condition_length = _derive_memory_condition_length(cfg)
37
+ super().__init__(cfg)
algorithms/dememwm/df_base.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This repo is forked from [Boyuan Chen](https://boyuan.space/)'s research
3
+ template [repo](https://github.com/buoyancy99/research-template).
4
+ By its MIT license, you must keep the above sentence in `README.md`
5
+ and the `LICENSE` file to credit the author.
6
+ """
7
+
8
+ from typing import Optional
9
+ from tqdm import tqdm
10
+ from omegaconf import DictConfig
11
+ import numpy as np
12
+ import torch
13
+ import torch.nn.functional as F
14
+ from typing import Any
15
+ from einops import rearrange
16
+
17
+ from lightning.pytorch.utilities.types import STEP_OUTPUT
18
+
19
+ from algorithms.common.base_pytorch_algo import BasePytorchAlgo
20
+ from .models.diffusion import Diffusion
21
+
22
+
23
+ class DiffusionForcingBase(BasePytorchAlgo):
24
+ def __init__(self, cfg: DictConfig):
25
+ self.cfg = cfg
26
+ self.x_shape = cfg.x_shape
27
+ self.frame_stack = cfg.frame_stack
28
+ self.x_stacked_shape = list(self.x_shape)
29
+ self.x_stacked_shape[0] *= cfg.frame_stack
30
+ self.guidance_scale = cfg.guidance_scale
31
+ self.context_frames = cfg.context_frames
32
+ self.chunk_size = cfg.chunk_size
33
+ self.action_cond_dim = cfg.action_cond_dim
34
+ self.causal = cfg.causal
35
+
36
+ self.uncertainty_scale = cfg.uncertainty_scale
37
+ self.timesteps = cfg.diffusion.timesteps
38
+ self.sampling_timesteps = cfg.diffusion.sampling_timesteps
39
+ self.clip_noise = cfg.diffusion.clip_noise
40
+
41
+ self.cfg.diffusion.cum_snr_decay = self.cfg.diffusion.cum_snr_decay ** (self.frame_stack * cfg.frame_skip)
42
+
43
+ self.validation_step_outputs = []
44
+ super().__init__(cfg)
45
+
46
+ def _build_model(self):
47
+ self.diffusion_model = Diffusion(
48
+ x_shape=self.x_stacked_shape,
49
+ action_cond_dim=self.action_cond_dim,
50
+ is_causal=self.causal,
51
+ cfg=self.cfg.diffusion,
52
+ )
53
+ self.register_data_mean_std(self.cfg.data_mean, self.cfg.data_std)
54
+
55
+ def configure_optimizers(self):
56
+ params = tuple(self.diffusion_model.parameters())
57
+ optimizer_dynamics = torch.optim.AdamW(
58
+ params, lr=self.cfg.lr, weight_decay=self.cfg.weight_decay, betas=self.cfg.optimizer_beta
59
+ )
60
+ return optimizer_dynamics
61
+
62
+ def optimizer_step(self, epoch, batch_idx, optimizer, optimizer_closure):
63
+ # update params
64
+ optimizer.step(closure=optimizer_closure)
65
+
66
+ # manually warm up lr without a scheduler
67
+ if self.trainer.global_step < self.cfg.warmup_steps:
68
+ lr_scale = min(1.0, float(self.trainer.global_step + 1) / self.cfg.warmup_steps)
69
+ for pg in optimizer.param_groups:
70
+ pg["lr"] = lr_scale * self.cfg.lr
71
+
72
+ def training_step(self, batch, batch_idx) -> STEP_OUTPUT:
73
+ xs, conditions, masks = self._preprocess_batch(batch)
74
+
75
+ rand_length = torch.randint(3,xs.shape[0]-2, (1,))[0].item()
76
+ xs = torch.cat([xs[:rand_length], xs[rand_length-3:rand_length-1]])
77
+ conditions = torch.cat([conditions[:rand_length], conditions[rand_length-3:rand_length-1]])
78
+ masks = torch.cat([masks[:rand_length], masks[rand_length-3:rand_length-1]])
79
+ noise_levels=self._generate_noise_levels(xs)
80
+ noise_levels[:rand_length] = 15 # stable_noise_levels
81
+ noise_levels[rand_length+1:] = 15 # stable_noise_levels
82
+
83
+ xs_pred, loss = self.diffusion_model(xs, conditions, noise_levels=noise_levels)
84
+ loss = self.reweight_loss(loss, masks)
85
+
86
+ # log the loss
87
+ if batch_idx % 20 == 0:
88
+ self.log("training/loss", loss)
89
+
90
+ xs = self._unstack_and_unnormalize(xs)
91
+ xs_pred = self._unstack_and_unnormalize(xs_pred)
92
+
93
+ output_dict = {
94
+ "loss": loss,
95
+ "xs_pred": xs_pred,
96
+ "xs": xs,
97
+ }
98
+
99
+ return output_dict
100
+
101
+ @torch.no_grad()
102
+ def validation_step(self, batch, batch_idx, namespace="validation") -> STEP_OUTPUT:
103
+ xs, conditions, masks = self._preprocess_batch(batch)
104
+ n_frames, batch_size, *_ = xs.shape
105
+ xs_pred = []
106
+ curr_frame = 0
107
+
108
+ # context
109
+ n_context_frames = self.context_frames // self.frame_stack
110
+ xs_pred = xs[:n_context_frames].clone()
111
+ curr_frame += n_context_frames
112
+
113
+ if self.condtion_similar_length:
114
+ n_frames -= self.condtion_similar_length
115
+
116
+ pbar = tqdm(total=n_frames, initial=curr_frame, desc="Sampling")
117
+ while curr_frame < n_frames:
118
+ if self.chunk_size > 0:
119
+ horizon = min(n_frames - curr_frame, self.chunk_size)
120
+ else:
121
+ horizon = n_frames - curr_frame
122
+ assert horizon <= self.n_tokens, "horizon exceeds the number of tokens."
123
+ scheduling_matrix = self._generate_scheduling_matrix(horizon)
124
+
125
+ chunk = torch.randn((horizon, batch_size, *self.x_stacked_shape), device=self.device)
126
+ chunk = torch.clamp(chunk, -self.clip_noise, self.clip_noise)
127
+ xs_pred = torch.cat([xs_pred, chunk], 0)
128
+
129
+ # sliding window: only input the last n_tokens frames
130
+ start_frame = max(0, curr_frame + horizon - self.n_tokens)
131
+
132
+ pbar.set_postfix(
133
+ {
134
+ "start": start_frame,
135
+ "end": curr_frame + horizon,
136
+ }
137
+ )
138
+
139
+ if self.condtion_similar_length:
140
+ xs_pred = torch.cat([xs_pred, xs[curr_frame-self.condtion_similar_length:curr_frame].clone()], 0)
141
+
142
+ for m in range(scheduling_matrix.shape[0] - 1):
143
+
144
+ from_noise_levels = np.concatenate((np.zeros((curr_frame,), dtype=np.int64), scheduling_matrix[m]))[
145
+ :, None
146
+ ].repeat(batch_size, axis=1)
147
+ to_noise_levels = np.concatenate(
148
+ (
149
+ np.zeros((curr_frame,), dtype=np.int64),
150
+ scheduling_matrix[m + 1],
151
+ )
152
+ )[
153
+ :, None
154
+ ].repeat(batch_size, axis=1)
155
+
156
+ if self.condtion_similar_length:
157
+ from_noise_levels = np.concatenate([from_noise_levels, np.array([[0,0,0,0]*self.condtion_similar_length])], axis=0)
158
+ to_noise_levels = np.concatenate([to_noise_levels, np.array([[0,0,0,0]*self.condtion_similar_length])], axis=0)
159
+
160
+ from_noise_levels = torch.from_numpy(from_noise_levels).to(self.device)
161
+ to_noise_levels = torch.from_numpy(to_noise_levels).to(self.device)
162
+
163
+ # update xs_pred by DDIM or DDPM sampling
164
+ # input frames within the sliding window
165
+
166
+ try:
167
+ input_condition = conditions[start_frame : curr_frame + horizon].clone()
168
+ except:
169
+ import pdb;pdb.set_trace()
170
+ if self.condtion_similar_length:
171
+ input_condition = torch.cat([conditions[start_frame : curr_frame + horizon], conditions[-self.condtion_similar_length:]], dim=0)
172
+ xs_pred[start_frame:] = self.diffusion_model.sample_step(
173
+ xs_pred[start_frame:],
174
+ input_condition,
175
+ from_noise_levels[start_frame:],
176
+ to_noise_levels[start_frame:],
177
+ )
178
+
179
+ if self.condtion_similar_length:
180
+ xs_pred = xs_pred[:-self.condtion_similar_length]
181
+
182
+ curr_frame += horizon
183
+ pbar.update(horizon)
184
+
185
+ if self.condtion_similar_length:
186
+ xs = xs[:-self.condtion_similar_length]
187
+ # FIXME: loss
188
+ loss = F.mse_loss(xs_pred, xs, reduction="none")
189
+ loss = self.reweight_loss(loss, masks)
190
+ self.validation_step_outputs.append((xs_pred.detach().cpu(), xs.detach().cpu()))
191
+
192
+ return loss
193
+
194
+ def test_step(self, *args: Any, **kwargs: Any) -> STEP_OUTPUT:
195
+ return self.validation_step(*args, **kwargs, namespace="test")
196
+
197
+ def on_test_epoch_end(self) -> None:
198
+ self.on_validation_epoch_end(namespace="test")
199
+
200
+ def _generate_noise_levels(self, xs: torch.Tensor, masks: Optional[torch.Tensor] = None) -> torch.Tensor:
201
+ """
202
+ Generate noise levels for training.
203
+ """
204
+ num_frames, batch_size, *_ = xs.shape
205
+ match self.cfg.noise_level:
206
+ case "random_all": # entirely random noise levels
207
+ noise_levels = torch.randint(0, self.timesteps, (num_frames, batch_size), device=xs.device)
208
+ case "same":
209
+ noise_levels = torch.randint(0, self.timesteps, (num_frames, batch_size), device=xs.device)
210
+ noise_levels[1:] = noise_levels[0]
211
+
212
+ if masks is not None:
213
+ # for frames that are not available, treat as full noise
214
+ discard = torch.all(~rearrange(masks.bool(), "(t fs) b -> t b fs", fs=self.frame_stack), -1)
215
+ noise_levels = torch.where(discard, torch.full_like(noise_levels, self.timesteps - 1), noise_levels)
216
+
217
+ return noise_levels
218
+
219
+ def _generate_scheduling_matrix(self, horizon: int):
220
+ match self.cfg.scheduling_matrix:
221
+ case "pyramid":
222
+ return self._generate_pyramid_scheduling_matrix(horizon, self.uncertainty_scale)
223
+ case "full_sequence":
224
+ return np.arange(self.sampling_timesteps, -1, -1)[:, None].repeat(horizon, axis=1)
225
+ case "autoregressive":
226
+ return self._generate_pyramid_scheduling_matrix(horizon, self.sampling_timesteps)
227
+ case "trapezoid":
228
+ return self._generate_trapezoid_scheduling_matrix(horizon, self.uncertainty_scale)
229
+
230
+ def _generate_pyramid_scheduling_matrix(self, horizon: int, uncertainty_scale: float):
231
+ height = self.sampling_timesteps + int((horizon - 1) * uncertainty_scale) + 1
232
+ scheduling_matrix = np.zeros((height, horizon), dtype=np.int64)
233
+ for m in range(height):
234
+ for t in range(horizon):
235
+ scheduling_matrix[m, t] = self.sampling_timesteps + int(t * uncertainty_scale) - m
236
+
237
+ return np.clip(scheduling_matrix, 0, self.sampling_timesteps)
238
+
239
+ def _generate_trapezoid_scheduling_matrix(self, horizon: int, uncertainty_scale: float):
240
+ height = self.sampling_timesteps + int((horizon + 1) // 2 * uncertainty_scale)
241
+ scheduling_matrix = np.zeros((height, horizon), dtype=np.int64)
242
+ for m in range(height):
243
+ for t in range((horizon + 1) // 2):
244
+ scheduling_matrix[m, t] = self.sampling_timesteps + int(t * uncertainty_scale) - m
245
+ scheduling_matrix[m, -t] = self.sampling_timesteps + int(t * uncertainty_scale) - m
246
+
247
+ return np.clip(scheduling_matrix, 0, self.sampling_timesteps)
248
+
249
+ def reweight_loss(self, loss, weight=None):
250
+ # Note there is another part of loss reweighting (fused_snr) inside the Diffusion class!
251
+ loss = rearrange(loss, "t b (fs c) ... -> t b fs c ...", fs=self.frame_stack)
252
+ if weight is not None:
253
+ expand_dim = len(loss.shape) - len(weight.shape) - 1
254
+ weight = rearrange(
255
+ weight,
256
+ "(t fs) b ... -> t b fs ..." + " 1" * expand_dim,
257
+ fs=self.frame_stack,
258
+ )
259
+ loss = loss * weight
260
+
261
+ return loss.mean()
262
+
263
+ def _preprocess_batch(self, batch):
264
+ xs = batch[0]
265
+ batch_size, n_frames = xs.shape[:2]
266
+
267
+ if n_frames % self.frame_stack != 0:
268
+ raise ValueError("Number of frames must be divisible by frame stack size")
269
+ if self.context_frames % self.frame_stack != 0:
270
+ raise ValueError("Number of context frames must be divisible by frame stack size")
271
+
272
+ masks = torch.ones(n_frames, batch_size).to(xs.device)
273
+ n_frames = n_frames // self.frame_stack
274
+
275
+ if self.action_cond_dim:
276
+ conditions = batch[1]
277
+ conditions = torch.cat([torch.zeros_like(conditions[:, :1]), conditions[:, 1:]], 1)
278
+ conditions = rearrange(conditions, "b (t fs) d -> t b (fs d)", fs=self.frame_stack).contiguous()
279
+
280
+ # f, _, _ = conditions.shape
281
+ # predefined_1 = torch.tensor([0,0,0,1]).to(conditions.device)
282
+ # predefined_2 = torch.tensor([0,0,1,0]).to(conditions.device)
283
+ # conditions[:f//2] = predefined_1
284
+ # conditions[f//2:] = predefined_2
285
+ else:
286
+ conditions = [None for _ in range(n_frames)]
287
+
288
+ xs = self._normalize_x(xs)
289
+ xs = rearrange(xs, "b (t fs) c ... -> t b (fs c) ...", fs=self.frame_stack).contiguous()
290
+
291
+ return xs, conditions, masks
292
+
293
+ def _normalize_x(self, xs):
294
+ shape = [1] * (xs.ndim - self.data_mean.ndim) + list(self.data_mean.shape)
295
+ mean = self.data_mean.reshape(shape)
296
+ std = self.data_std.reshape(shape)
297
+ return (xs - mean) / std
298
+
299
+ def _unnormalize_x(self, xs):
300
+ shape = [1] * (xs.ndim - self.data_mean.ndim) + list(self.data_mean.shape)
301
+ mean = self.data_mean.reshape(shape)
302
+ std = self.data_std.reshape(shape)
303
+ return xs * std + mean
304
+
305
+ def _unstack_and_unnormalize(self, xs):
306
+ xs = rearrange(xs, "t b (fs c) ... -> (t fs) b c ...", fs=self.frame_stack)
307
+ return self._unnormalize_x(xs)
algorithms/dememwm/df_video.py ADDED
@@ -0,0 +1,954 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import math
4
+ import numpy as np
5
+ import torch
6
+ import torch.distributed as dist
7
+ import torch.nn.functional as F
8
+ import torchvision.transforms.functional as TF
9
+ from torchvision.transforms import InterpolationMode
10
+ from PIL import Image
11
+ from packaging import version as pver
12
+ from einops import rearrange
13
+ from tqdm import tqdm
14
+ from omegaconf import DictConfig
15
+ from lightning.pytorch.utilities.types import STEP_OUTPUT
16
+ from algorithms.common.metrics import (
17
+ LearnedPerceptualImagePatchSimilarity,
18
+ )
19
+ from utils.logging_utils import log_video, get_validation_metrics_for_videos
20
+ from .df_base import DiffusionForcingBase
21
+ from .models.vae import VAE_models
22
+ from .models.diffusion import Diffusion
23
+ from .models.pose_prediction import PosePredictionNet
24
+ import glob
25
+
26
+ # Utility Functions
27
+ def euler_to_rotation_matrix(pitch, yaw):
28
+ """
29
+ Convert pitch and yaw angles (in radians) to a 3x3 rotation matrix.
30
+ Supports batch input.
31
+
32
+ Args:
33
+ pitch (torch.Tensor): Pitch angles in radians.
34
+ yaw (torch.Tensor): Yaw angles in radians.
35
+
36
+ Returns:
37
+ torch.Tensor: Rotation matrix of shape (batch_size, 3, 3).
38
+ """
39
+ cos_pitch, sin_pitch = torch.cos(pitch), torch.sin(pitch)
40
+ cos_yaw, sin_yaw = torch.cos(yaw), torch.sin(yaw)
41
+
42
+ R_pitch = torch.stack([
43
+ torch.ones_like(pitch), torch.zeros_like(pitch), torch.zeros_like(pitch),
44
+ torch.zeros_like(pitch), cos_pitch, -sin_pitch,
45
+ torch.zeros_like(pitch), sin_pitch, cos_pitch
46
+ ], dim=-1).reshape(-1, 3, 3)
47
+
48
+ R_yaw = torch.stack([
49
+ cos_yaw, torch.zeros_like(yaw), sin_yaw,
50
+ torch.zeros_like(yaw), torch.ones_like(yaw), torch.zeros_like(yaw),
51
+ -sin_yaw, torch.zeros_like(yaw), cos_yaw
52
+ ], dim=-1).reshape(-1, 3, 3)
53
+
54
+ return torch.matmul(R_yaw, R_pitch)
55
+
56
+
57
+ def euler_to_camera_to_world_matrix(pose):
58
+ """
59
+ Convert (x, y, z, pitch, yaw) to a 4x4 camera-to-world transformation matrix using torch.
60
+ Supports both (5,) and (f, b, 5) shaped inputs.
61
+
62
+ Args:
63
+ pose (torch.Tensor): Pose tensor of shape (5,) or (f, b, 5).
64
+
65
+ Returns:
66
+ torch.Tensor: Camera-to-world transformation matrix of shape (4, 4).
67
+ """
68
+
69
+ origin_dim = pose.ndim
70
+ if origin_dim == 1:
71
+ pose = pose.unsqueeze(0).unsqueeze(0) # Convert (5,) -> (1, 1, 5)
72
+ elif origin_dim == 2:
73
+ pose = pose.unsqueeze(0)
74
+
75
+ x, y, z, pitch, yaw = pose[..., 0], pose[..., 1], pose[..., 2], pose[..., 3], pose[..., 4]
76
+ pitch, yaw = torch.deg2rad(pitch), torch.deg2rad(yaw)
77
+
78
+ # Compute rotation matrix (batch mode)
79
+ R = euler_to_rotation_matrix(pitch, yaw) # Shape (f*b, 3, 3)
80
+
81
+ # Create the 4x4 transformation matrix
82
+ eye = torch.eye(4, dtype=torch.float32, device=pose.device)
83
+ camera_to_world = eye.repeat(R.shape[0], 1, 1) # Shape (f*b, 4, 4)
84
+
85
+ # Assign rotation
86
+ camera_to_world[:, :3, :3] = R
87
+
88
+ # Assign translation
89
+ camera_to_world[:, :3, 3] = torch.stack([x.reshape(-1), y.reshape(-1), z.reshape(-1)], dim=-1)
90
+
91
+ # Reshape back to (f, b, 4, 4) if needed
92
+ if origin_dim == 3:
93
+ return camera_to_world.view(pose.shape[0], pose.shape[1], 4, 4)
94
+ elif origin_dim == 2:
95
+ return camera_to_world.view(pose.shape[0], 4, 4)
96
+ else:
97
+ return camera_to_world.squeeze(0).squeeze(0) # Convert (1,1,4,4) -> (4,4)
98
+
99
+ def is_inside_fov_3d_hv(points, center, center_pitch, center_yaw, fov_half_h, fov_half_v):
100
+ """
101
+ Check whether points are within a given 3D field of view (FOV)
102
+ with separately defined horizontal and vertical ranges.
103
+
104
+ The center view direction is specified by pitch and yaw (in degrees).
105
+
106
+ :param points: (N, B, 3) Sample point coordinates
107
+ :param center: (3,) Center coordinates of the FOV
108
+ :param center_pitch: Pitch angle of the center view (in degrees)
109
+ :param center_yaw: Yaw angle of the center view (in degrees)
110
+ :param fov_half_h: Horizontal half-FOV angle (in degrees)
111
+ :param fov_half_v: Vertical half-FOV angle (in degrees)
112
+ :return: Boolean tensor (N, B), indicating whether each point is inside the FOV
113
+ """
114
+ # Compute vectors relative to the center
115
+ vectors = points - center # shape (N, B, 3)
116
+ x = vectors[..., 0]
117
+ y = vectors[..., 1]
118
+ z = vectors[..., 2]
119
+
120
+ # Compute horizontal angle (yaw): measured with respect to the z-axis as the forward direction,
121
+ # and the x-axis as left-right, resulting in a range of -180 to 180 degrees.
122
+ azimuth = torch.atan2(x, z) * (180 / math.pi)
123
+
124
+ # Compute vertical angle (pitch): measured with respect to the horizontal plane,
125
+ # resulting in a range of -90 to 90 degrees.
126
+ elevation = torch.atan2(y, torch.sqrt(x**2 + z**2)) * (180 / math.pi)
127
+
128
+ # Compute the angular difference from the center view (handling circular angle wrap-around)
129
+ diff_azimuth = (azimuth - center_yaw).abs() % 360
130
+ diff_elevation = (elevation - center_pitch).abs() % 360
131
+
132
+ # Adjust values greater than 180 degrees to the shorter angular difference
133
+ diff_azimuth = torch.where(diff_azimuth > 180, 360 - diff_azimuth, diff_azimuth)
134
+ diff_elevation = torch.where(diff_elevation > 180, 360 - diff_elevation, diff_elevation)
135
+
136
+ # Check if both horizontal and vertical angles are within their respective FOV limits
137
+ return (diff_azimuth < fov_half_h) & (diff_elevation < fov_half_v)
138
+
139
+ def generate_points_in_sphere(n_points, radius):
140
+ # Sample three independent uniform distributions
141
+ samples_r = torch.rand(n_points) # For radius distribution
142
+ samples_phi = torch.rand(n_points) # For azimuthal angle phi
143
+ samples_u = torch.rand(n_points) # For polar angle theta
144
+
145
+ # Apply cube root to ensure uniform volumetric distribution
146
+ r = radius * torch.pow(samples_r, 1/3)
147
+ # Azimuthal angle phi uniformly distributed in [0, 2π]
148
+ phi = 2 * math.pi * samples_phi
149
+ # Convert u to theta to ensure cos(theta) is uniformly distributed
150
+ theta = torch.acos(1 - 2 * samples_u)
151
+
152
+ # Convert spherical coordinates to Cartesian coordinates
153
+ x = r * torch.sin(theta) * torch.cos(phi)
154
+ y = r * torch.sin(theta) * torch.sin(phi)
155
+ z = r * torch.cos(theta)
156
+
157
+ points = torch.stack((x, y, z), dim=1)
158
+ return points
159
+
160
+ def tensor_max_with_number(tensor, number):
161
+ number_tensor = torch.tensor(number, dtype=tensor.dtype, device=tensor.device)
162
+ result = torch.max(tensor, number_tensor)
163
+ return result
164
+
165
+ def custom_meshgrid(*args):
166
+ # ref: https://pytorch.org/docs/stable/generated/torch.meshgrid.html?highlight=meshgrid#torch.meshgrid
167
+ if pver.parse(torch.__version__) < pver.parse('1.10'):
168
+ return torch.meshgrid(*args)
169
+ else:
170
+ return torch.meshgrid(*args, indexing='ij')
171
+
172
+ def camera_to_world_to_world_to_camera(camera_to_world: torch.Tensor) -> torch.Tensor:
173
+ """
174
+ Convert Camera-to-World matrices to World-to-Camera matrices for a tensor with shape (f, b, 4, 4).
175
+
176
+ Args:
177
+ camera_to_world (torch.Tensor): A tensor of shape (f, b, 4, 4), where:
178
+ f = number of frames,
179
+ b = batch size.
180
+
181
+ Returns:
182
+ torch.Tensor: A tensor of shape (f, b, 4, 4) representing the World-to-Camera matrices.
183
+ """
184
+ # Ensure input is a 4D tensor
185
+ assert camera_to_world.ndim == 4 and camera_to_world.shape[2:] == (4, 4), \
186
+ "Input must be of shape (f, b, 4, 4)"
187
+
188
+ # Extract the rotation (R) and translation (T) parts
189
+ R = camera_to_world[:, :, :3, :3] # Shape: (f, b, 3, 3)
190
+ T = camera_to_world[:, :, :3, 3] # Shape: (f, b, 3)
191
+
192
+ # Initialize an identity matrix for the output
193
+ world_to_camera = torch.eye(4, device=camera_to_world.device).unsqueeze(0).unsqueeze(0)
194
+ world_to_camera = world_to_camera.repeat(camera_to_world.size(0), camera_to_world.size(1), 1, 1) # Shape: (f, b, 4, 4)
195
+
196
+ # Compute the rotation (transpose of R)
197
+ world_to_camera[:, :, :3, :3] = R.transpose(2, 3)
198
+
199
+ # Compute the translation (-R^T * T)
200
+ world_to_camera[:, :, :3, 3] = -torch.matmul(R.transpose(2, 3), T.unsqueeze(-1)).squeeze(-1)
201
+
202
+ return world_to_camera.to(camera_to_world.dtype)
203
+
204
+ def convert_to_plucker(poses, curr_frame, focal_length, image_width, image_height):
205
+
206
+ intrinsic = np.asarray([focal_length * image_width,
207
+ focal_length * image_height,
208
+ 0.5 * image_width,
209
+ 0.5 * image_height], dtype=np.float32)
210
+
211
+ c2ws = get_relative_pose(poses, zero_first_frame_scale=curr_frame)
212
+ c2ws = rearrange(c2ws, "t b m n -> b t m n")
213
+
214
+ K = torch.as_tensor(intrinsic, device=poses.device, dtype=poses.dtype).repeat(c2ws.shape[0],c2ws.shape[1],1) # [B, F, 4]
215
+ plucker_embedding = ray_condition(K, c2ws, image_height, image_width, device=c2ws.device)
216
+ plucker_embedding = rearrange(plucker_embedding, "b t h w d -> t b h w d").contiguous()
217
+
218
+ return plucker_embedding
219
+
220
+
221
+ def get_relative_pose(abs_c2ws, zero_first_frame_scale):
222
+ abs_w2cs = camera_to_world_to_world_to_camera(abs_c2ws)
223
+ target_cam_c2w = torch.tensor([
224
+ [1, 0, 0, 0],
225
+ [0, 1, 0, 0],
226
+ [0, 0, 1, 0],
227
+ [0, 0, 0, 1]
228
+ ]).to(abs_c2ws.device).to(abs_c2ws.dtype)
229
+ abs2rel = target_cam_c2w @ abs_w2cs[zero_first_frame_scale]
230
+ ret_poses = [abs2rel @ abs_c2w for abs_c2w in abs_c2ws]
231
+ ret_poses = torch.stack(ret_poses)
232
+ return ret_poses
233
+
234
+ def ray_condition(K, c2w, H, W, device):
235
+ # c2w: B, V, 4, 4
236
+ # K: B, V, 4
237
+
238
+ B = K.shape[0]
239
+
240
+ j, i = custom_meshgrid(
241
+ torch.linspace(0, H - 1, H, device=device, dtype=c2w.dtype),
242
+ torch.linspace(0, W - 1, W, device=device, dtype=c2w.dtype),
243
+ )
244
+ i = i.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW]
245
+ j = j.reshape([1, 1, H * W]).expand([B, 1, H * W]) + 0.5 # [B, HxW]
246
+
247
+ fx, fy, cx, cy = K.chunk(4, dim=-1) # B,V, 1
248
+
249
+ zs = torch.ones_like(i, device=device, dtype=c2w.dtype) # [B, HxW]
250
+ xs = -(i - cx) / fx * zs
251
+ ys = -(j - cy) / fy * zs
252
+
253
+ zs = zs.expand_as(ys)
254
+
255
+ directions = torch.stack((xs, ys, zs), dim=-1) # B, V, HW, 3
256
+ directions = directions / directions.norm(dim=-1, keepdim=True) # B, V, HW, 3
257
+
258
+ rays_d = directions @ c2w[..., :3, :3].transpose(-1, -2) # B, V, 3, HW
259
+ rays_o = c2w[..., :3, 3] # B, V, 3
260
+ rays_o = rays_o[:, :, None].expand_as(rays_d) # B, V, 3, HW
261
+ # c2w @ dirctions
262
+ rays_dxo = torch.linalg.cross(rays_o, rays_d)
263
+ plucker = torch.cat([rays_dxo, rays_d], dim=-1)
264
+ plucker = plucker.reshape(B, c2w.shape[1], H, W, 6) # B, V, H, W, 6
265
+
266
+ return plucker
267
+
268
+ def random_transform(tensor):
269
+ """
270
+ Apply the same random translation, rotation, and scaling to all frames in the batch.
271
+
272
+ Args:
273
+ tensor (torch.Tensor): Input tensor of shape (F, B, 3, H, W).
274
+
275
+ Returns:
276
+ torch.Tensor: Transformed tensor of shape (F, B, 3, H, W).
277
+ """
278
+ if tensor.ndim != 5:
279
+ raise ValueError("Input tensor must have shape (F, B, 3, H, W)")
280
+
281
+ F, B, C, H, W = tensor.shape
282
+
283
+ # Generate random transformation parameters
284
+ max_translate = 0.2 # Translate up to 20% of width/height
285
+ max_rotate = 30 # Rotate up to 30 degrees
286
+ max_scale = 0.2 # Scale change by up to +/- 20%
287
+
288
+ translate_x = random.uniform(-max_translate, max_translate) * W
289
+ translate_y = random.uniform(-max_translate, max_translate) * H
290
+ rotate_angle = random.uniform(-max_rotate, max_rotate)
291
+ scale_factor = 1 + random.uniform(-max_scale, max_scale)
292
+
293
+ # Apply the same transformation to all frames and batches
294
+
295
+ tensor = tensor.reshape(F*B, C, H, W)
296
+ transformed_tensor = TF.affine(
297
+ tensor,
298
+ angle=rotate_angle,
299
+ translate=(translate_x, translate_y),
300
+ scale=scale_factor,
301
+ shear=(0, 0),
302
+ interpolation=InterpolationMode.BILINEAR,
303
+ fill=0
304
+ )
305
+
306
+ transformed_tensor = transformed_tensor.reshape(F, B, C, H, W)
307
+ return transformed_tensor
308
+
309
+ def save_tensor_as_png(tensor, file_path):
310
+ """
311
+ Save a 3*H*W tensor as a PNG image.
312
+
313
+ Args:
314
+ tensor (torch.Tensor): Input tensor of shape (3, H, W).
315
+ file_path (str): Path to save the PNG file.
316
+ """
317
+ if tensor.ndim != 3 or tensor.shape[0] != 3:
318
+ raise ValueError("Input tensor must have shape (3, H, W)")
319
+
320
+ # Convert tensor to PIL Image
321
+ image = TF.to_pil_image(tensor)
322
+
323
+ # Save image
324
+ image.save(file_path)
325
+
326
+ class WorldMemMinecraft(DiffusionForcingBase):
327
+ """
328
+ Video generation for MineCraft with memory.
329
+ """
330
+
331
+ def __init__(self, cfg: DictConfig):
332
+ """
333
+ Initialize the WorldMemMinecraft class with the given configuration.
334
+
335
+ Args:
336
+ cfg (DictConfig): Configuration object.
337
+ """
338
+ self.n_tokens = cfg.n_frames // cfg.frame_stack # number of max tokens for the model
339
+ self.n_frames = cfg.n_frames
340
+ if hasattr(cfg, "n_tokens"):
341
+ self.n_tokens = cfg.n_tokens // cfg.frame_stack
342
+ self.memory_condition_length = cfg.memory_condition_length
343
+ self.pose_cond_dim = getattr(cfg, "pose_cond_dim", 5)
344
+
345
+ self.use_plucker = getattr(cfg, "use_plucker", True)
346
+ self.relative_embedding = getattr(cfg, "relative_embedding", True)
347
+ self.state_embed_only_on_qk = getattr(cfg, "state_embed_only_on_qk", True)
348
+ self.use_memory_attention = getattr(cfg, "use_memory_attention", True)
349
+ self.add_timestamp_embedding = getattr(cfg, "add_timestamp_embedding", True)
350
+ self.ref_mode = getattr(cfg, "ref_mode", 'sequential')
351
+ self.log_curve = getattr(cfg, "log_curve", False)
352
+ self.focal_length = getattr(cfg, "focal_length", 0.35)
353
+ self.log_video = cfg.log_video
354
+ self.save_local = getattr(cfg, "save_local", True)
355
+ self.local_save_dir = getattr(cfg, "local_save_dir", None)
356
+ self.lpips_batch_size = getattr(cfg, "lpips_batch_size", 16)
357
+ self.next_frame_length = getattr(cfg, "next_frame_length", 1)
358
+ self.require_pose_prediction = getattr(cfg, "require_pose_prediction", False)
359
+
360
+ super().__init__(cfg)
361
+
362
+ def _build_model(self):
363
+
364
+ self.diffusion_model = Diffusion(
365
+ reference_length=self.memory_condition_length,
366
+ x_shape=self.x_stacked_shape,
367
+ action_cond_dim=self.action_cond_dim,
368
+ pose_cond_dim=self.pose_cond_dim,
369
+ is_causal=self.causal,
370
+ cfg=self.cfg.diffusion,
371
+ is_dit=True,
372
+ use_plucker=self.use_plucker,
373
+ relative_embedding=self.relative_embedding,
374
+ state_embed_only_on_qk=self.state_embed_only_on_qk,
375
+ use_memory_attention=self.use_memory_attention,
376
+ add_timestamp_embedding=self.add_timestamp_embedding,
377
+ ref_mode=self.ref_mode
378
+ )
379
+
380
+ self.validation_lpips_model = LearnedPerceptualImagePatchSimilarity(sync_on_compute=False)
381
+ vae = VAE_models["vit-l-20-shallow-encoder"]()
382
+ self.vae = vae.eval()
383
+
384
+ if self.require_pose_prediction:
385
+ self.pose_prediction_model = PosePredictionNet()
386
+
387
+ def _generate_noise_levels(self, xs: torch.Tensor, masks = None) -> torch.Tensor:
388
+ """
389
+ Generate noise levels for training.
390
+ """
391
+ num_frames, batch_size, *_ = xs.shape
392
+ match self.cfg.noise_level:
393
+ case "random_all": # entirely random noise levels
394
+ noise_levels = torch.randint(0, self.timesteps, (num_frames, batch_size), device=xs.device)
395
+ case "same":
396
+ noise_levels = torch.randint(0, self.timesteps, (num_frames, batch_size), device=xs.device)
397
+ noise_levels[1:] = noise_levels[0]
398
+
399
+ if masks is not None:
400
+ # for frames that are not available, treat as full noise
401
+ discard = torch.all(~rearrange(masks.bool(), "(t fs) b -> t b fs", fs=self.frame_stack), -1)
402
+ noise_levels = torch.where(discard, torch.full_like(noise_levels, self.timesteps - 1), noise_levels)
403
+
404
+ return noise_levels
405
+
406
+ def training_step(self, batch, batch_idx) -> STEP_OUTPUT:
407
+ """
408
+ Perform a single training step.
409
+
410
+ This function processes the input batch,
411
+ encodes the input frames, generates noise levels, and computes the loss using the diffusion model.
412
+
413
+ Args:
414
+ batch: Input batch of data containing frames, conditions, poses, etc.
415
+ batch_idx: Index of the current batch.
416
+
417
+ Returns:
418
+ dict: A dictionary containing the training loss.
419
+ """
420
+ xs, conditions, pose_conditions, c2w_mat, frame_idx = self._preprocess_batch(batch)
421
+
422
+ if self.use_plucker:
423
+ if self.relative_embedding:
424
+ input_pose_condition = []
425
+ frame_idx_list = []
426
+ for i in range(self.n_frames):
427
+ input_pose_condition.append(
428
+ convert_to_plucker(
429
+ torch.cat([c2w_mat[i:i + 1], c2w_mat[-self.memory_condition_length:]]).clone(),
430
+ 0,
431
+ focal_length=self.focal_length,
432
+ image_height=xs.shape[-2],image_width=xs.shape[-1]
433
+ ).to(xs.dtype)
434
+ )
435
+ frame_idx_list.append(
436
+ torch.cat([
437
+ frame_idx[i:i + 1] - frame_idx[i:i + 1],
438
+ frame_idx[-self.memory_condition_length:] - frame_idx[i:i + 1]
439
+ ]).clone()
440
+ )
441
+ input_pose_condition = torch.cat(input_pose_condition)
442
+ frame_idx_list = torch.cat(frame_idx_list)
443
+ else:
444
+ input_pose_condition = convert_to_plucker(
445
+ c2w_mat, 0, focal_length=self.focal_length
446
+ ).to(xs.dtype)
447
+ frame_idx_list = frame_idx
448
+ else:
449
+ input_pose_condition = pose_conditions.to(xs.dtype)
450
+ frame_idx_list = None
451
+
452
+ xs = self.encode(xs)
453
+
454
+ noise_levels = self._generate_noise_levels(xs)
455
+
456
+ if self.memory_condition_length:
457
+ noise_levels[-self.memory_condition_length:] = self.diffusion_model.stabilization_level
458
+ conditions[-self.memory_condition_length:] *= 0
459
+
460
+ _, loss = self.diffusion_model(
461
+ xs,
462
+ conditions,
463
+ input_pose_condition,
464
+ noise_levels=noise_levels,
465
+ reference_length=self.memory_condition_length,
466
+ frame_idx=frame_idx_list
467
+ )
468
+
469
+ if self.memory_condition_length:
470
+ loss = loss[:-self.memory_condition_length]
471
+
472
+ loss = self.reweight_loss(loss, None)
473
+
474
+ if batch_idx % 20 == 0:
475
+ self.log("training/loss", loss.cpu())
476
+
477
+ return {"loss": loss}
478
+
479
+ def on_validation_epoch_start(self) -> None:
480
+ self._reset_metric_accumulators()
481
+
482
+ def on_test_epoch_start(self) -> None:
483
+ self._reset_metric_accumulators()
484
+
485
+ def on_validation_epoch_end(self) -> None:
486
+ self._on_eval_epoch_end()
487
+
488
+ def on_test_epoch_end(self) -> None:
489
+ self._on_eval_epoch_end()
490
+
491
+ def _reset_metric_accumulators(self) -> None:
492
+ self._metric_device = next(self.validation_lpips_model.parameters()).device
493
+ self._mse_sum = torch.tensor(0.0, device=self._metric_device)
494
+ self._mse_count = torch.tensor(0.0, device=self._metric_device)
495
+ self._psnr_sum = torch.tensor(0.0, device=self._metric_device)
496
+ self._psnr_count = torch.tensor(0.0, device=self._metric_device)
497
+ self._lpips_sum = torch.tensor(0.0, device=self._metric_device)
498
+ self._lpips_count = torch.tensor(0.0, device=self._metric_device)
499
+
500
+ def _update_metric_accumulators(self, xs_pred: torch.Tensor, xs_gt: torch.Tensor) -> None:
501
+ if not hasattr(self, "_metric_device"):
502
+ self._reset_metric_accumulators()
503
+
504
+ xs_pred_device = xs_pred.to(self._metric_device)
505
+ xs_gt_device = xs_gt.to(self._metric_device)
506
+ metric_dict = get_validation_metrics_for_videos(
507
+ xs_pred_device,
508
+ xs_gt_device,
509
+ lpips_model=self.validation_lpips_model,
510
+ lpips_batch_size=self.lpips_batch_size,
511
+ )
512
+
513
+ mse_count = torch.tensor(float(xs_pred_device.numel()), device=self._metric_device)
514
+ psnr_count = torch.tensor(float(xs_pred_device.shape[1]), device=self._metric_device)
515
+ lpips_count = torch.tensor(float(xs_pred_device.shape[0] * xs_pred_device.shape[1]), device=self._metric_device)
516
+
517
+ self._mse_sum += metric_dict["mse"].detach() * mse_count
518
+ self._mse_count += mse_count
519
+ self._psnr_sum += metric_dict["psnr"].detach() * psnr_count
520
+ self._psnr_count += psnr_count
521
+ self._lpips_sum += torch.tensor(float(metric_dict["lpips"]), device=self._metric_device) * lpips_count
522
+ self._lpips_count += lpips_count
523
+
524
+ del xs_pred_device, xs_gt_device
525
+
526
+ def _on_eval_epoch_end(self) -> None:
527
+ if not hasattr(self, "_metric_device"):
528
+ return
529
+
530
+ if dist.is_available() and dist.is_initialized():
531
+ for tensor in (
532
+ self._mse_sum,
533
+ self._mse_count,
534
+ self._psnr_sum,
535
+ self._psnr_count,
536
+ self._lpips_sum,
537
+ self._lpips_count,
538
+ ):
539
+ dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
540
+
541
+ if self._mse_count.item() > 0:
542
+ self.log_dict(
543
+ {
544
+ "mse": self._mse_sum / self._mse_count.clamp_min(1.0),
545
+ "psnr": self._psnr_sum / self._psnr_count.clamp_min(1.0),
546
+ "lpips": self._lpips_sum / self._lpips_count.clamp_min(1.0),
547
+ },
548
+ sync_dist=False,
549
+ )
550
+
551
+ self.validation_step_outputs.clear()
552
+
553
+ def _preprocess_batch(self, batch):
554
+
555
+ xs, conditions, pose_conditions, frame_index = batch
556
+
557
+ if self.action_cond_dim:
558
+ conditions = torch.cat([torch.zeros_like(conditions[:, :1]), conditions[:, 1:]], 1)
559
+ conditions = rearrange(conditions, "b t d -> t b d").contiguous()
560
+ else:
561
+ raise NotImplementedError("Only support external cond.")
562
+
563
+ pose_conditions = rearrange(pose_conditions, "b t d -> t b d").contiguous()
564
+ c2w_mat = euler_to_camera_to_world_matrix(pose_conditions)
565
+ xs = rearrange(xs, "b t c ... -> t b c ...").contiguous()
566
+ frame_index = rearrange(frame_index, "b t -> t b").contiguous()
567
+
568
+ return xs, conditions, pose_conditions, c2w_mat, frame_index
569
+
570
+ def encode(self, x):
571
+ # vae encoding
572
+ T = x.shape[0]
573
+ H, W = x.shape[-2:]
574
+ scaling_factor = 0.07843137255
575
+
576
+ x = rearrange(x, "t b c h w -> (t b) c h w")
577
+ with torch.no_grad():
578
+ x = self.vae.encode(x * 2 - 1).mean * scaling_factor
579
+ x = rearrange(x, "(t b) (h w) c -> t b c h w", t=T, h=H // self.vae.patch_size, w=W // self.vae.patch_size)
580
+ return x
581
+
582
+ def decode(self, x):
583
+ total_frames = x.shape[0]
584
+ scaling_factor = 0.07843137255
585
+ x = rearrange(x, "t b c h w -> (t b) (h w) c")
586
+ with torch.no_grad():
587
+ x = (self.vae.decode(x / scaling_factor) + 1) / 2
588
+ x = rearrange(x, "(t b) c h w-> t b c h w", t=total_frames)
589
+ return x
590
+
591
+ def _generate_condition_indices(self, curr_frame, memory_condition_length, xs_pred, pose_conditions, frame_idx, horizon):
592
+ """
593
+ Generate indices for condition similarity based on the current frame and pose conditions.
594
+ """
595
+ if curr_frame < memory_condition_length:
596
+ random_idx = [i for i in range(curr_frame)] + [0] * (memory_condition_length - curr_frame)
597
+ random_idx = np.repeat(np.array(random_idx)[:, None], xs_pred.shape[1], -1)
598
+ else:
599
+ # Generate points in a sphere and filter based on field of view
600
+ num_samples = 10000
601
+ radius = 30
602
+ points = generate_points_in_sphere(num_samples, radius).to(pose_conditions.device)
603
+ points = points[:, None].repeat(1, pose_conditions.shape[1], 1)
604
+ points += pose_conditions[curr_frame, :, :3][None]
605
+ fov_half_h = torch.tensor(105 / 2, device=pose_conditions.device)
606
+ fov_half_v = torch.tensor(75 / 2, device=pose_conditions.device)
607
+
608
+ # in_fov1 = is_inside_fov_3d_hv(
609
+ # points, pose_conditions[curr_frame, :, :3],
610
+ # pose_conditions[curr_frame, :, -2], pose_conditions[curr_frame, :, -1],
611
+ # fov_half_h, fov_half_v
612
+ # )
613
+
614
+ in_fov1 = torch.stack([
615
+ is_inside_fov_3d_hv(points, pc[:, :3], pc[:, -2], pc[:, -1], fov_half_h, fov_half_v)
616
+ for pc in pose_conditions[curr_frame:curr_frame+horizon]
617
+ ])
618
+
619
+ in_fov1 = torch.sum(in_fov1, 0) > 0
620
+
621
+ # Compute overlap ratios and select indices
622
+ in_fov_list = torch.stack([
623
+ is_inside_fov_3d_hv(points, pc[:, :3], pc[:, -2], pc[:, -1], fov_half_h, fov_half_v)
624
+ for pc in pose_conditions[:curr_frame]
625
+ ])
626
+
627
+ random_idx = []
628
+ for _ in range(memory_condition_length):
629
+ overlap_ratio = ((in_fov1.bool() & in_fov_list).sum(1)) / in_fov1.sum()
630
+
631
+ confidence = overlap_ratio + (curr_frame - frame_idx[:curr_frame]) / curr_frame * (-0.2)
632
+
633
+ if len(random_idx) > 0:
634
+ confidence[torch.cat(random_idx)] = -1e10
635
+ _, r_idx = torch.topk(confidence, k=1, dim=0)
636
+ random_idx.append(r_idx[0])
637
+
638
+ # choice 1: directly remove overlapping region
639
+ occupied_mask = in_fov_list[r_idx[0, range(in_fov1.shape[-1])], :, range(in_fov1.shape[-1])].permute(1,0)
640
+ in_fov1 = in_fov1 & ~occupied_mask
641
+
642
+ # choice 2: apply similarity filter
643
+ # cos_sim = F.cosine_similarity(xs_pred.to(r_idx.device)[r_idx[:, range(in_fov1.shape[1])],
644
+ # range(in_fov1.shape[1])], xs_pred.to(r_idx.device)[:curr_frame], dim=2)
645
+ # cos_sim = cos_sim.mean((-2,-1))
646
+
647
+ # mask_sim = cos_sim>0.9
648
+ # in_fov_list = in_fov_list & ~mask_sim[:,None].to(in_fov_list.device)
649
+
650
+ random_idx = torch.stack(random_idx).cpu()
651
+
652
+ return random_idx
653
+
654
+ def _prepare_conditions(self,
655
+ start_frame, curr_frame, horizon, conditions,
656
+ pose_conditions, c2w_mat, frame_idx, random_idx,
657
+ image_width, image_height):
658
+ """
659
+ Prepare input conditions and pose conditions for sampling.
660
+ """
661
+
662
+ padding = torch.zeros((len(random_idx),) + conditions.shape[1:], device=conditions.device, dtype=conditions.dtype)
663
+ input_condition = torch.cat([conditions[start_frame:curr_frame + horizon], padding], dim=0)
664
+
665
+ batch_size = conditions.shape[1]
666
+
667
+ if self.use_plucker:
668
+ if self.relative_embedding:
669
+ frame_idx_list = []
670
+ input_pose_condition = []
671
+ for i in range(start_frame, curr_frame + horizon):
672
+ input_pose_condition.append(convert_to_plucker(torch.cat([c2w_mat[i:i+1],c2w_mat[random_idx[:,range(batch_size)], range(batch_size)]]).clone(), 0, focal_length=self.focal_length,
673
+ image_width=image_width, image_height=image_height).to(conditions.dtype))
674
+ frame_idx_list.append(torch.cat([frame_idx[i:i+1]-frame_idx[i:i+1], frame_idx[random_idx[:,range(batch_size)], range(batch_size)]-frame_idx[i:i+1]]))
675
+ input_pose_condition = torch.cat(input_pose_condition)
676
+ frame_idx_list = torch.cat(frame_idx_list)
677
+
678
+ else:
679
+ input_pose_condition = torch.cat([c2w_mat[start_frame : curr_frame + horizon], c2w_mat[random_idx[:,range(batch_size)], range(batch_size)]], dim=0).clone()
680
+ input_pose_condition = convert_to_plucker(input_pose_condition, 0, focal_length=self.focal_length)
681
+ frame_idx_list = None
682
+ else:
683
+ input_pose_condition = torch.cat([pose_conditions[start_frame : curr_frame + horizon], pose_conditions[random_idx[:,range(batch_size)], range(batch_size)]], dim=0).clone()
684
+ frame_idx_list = None
685
+
686
+ return input_condition, input_pose_condition, frame_idx_list
687
+
688
+ def _prepare_noise_levels(self, scheduling_matrix, m, curr_frame, batch_size, memory_condition_length):
689
+ """
690
+ Prepare noise levels for the current sampling step.
691
+ """
692
+ from_noise_levels = np.concatenate((np.zeros((curr_frame,), dtype=np.int64), scheduling_matrix[m]))[:, None].repeat(batch_size, axis=1)
693
+ to_noise_levels = np.concatenate((np.zeros((curr_frame,), dtype=np.int64), scheduling_matrix[m + 1]))[:, None].repeat(batch_size, axis=1)
694
+ if memory_condition_length:
695
+ from_noise_levels = np.concatenate([from_noise_levels, np.zeros((memory_condition_length, from_noise_levels.shape[-1]), dtype=np.int32)], axis=0)
696
+ to_noise_levels = np.concatenate([to_noise_levels, np.zeros((memory_condition_length, from_noise_levels.shape[-1]), dtype=np.int32)], axis=0)
697
+ from_noise_levels = torch.from_numpy(from_noise_levels).to(self.device)
698
+ to_noise_levels = torch.from_numpy(to_noise_levels).to(self.device)
699
+ return from_noise_levels, to_noise_levels
700
+
701
+ def validation_step(self, batch, batch_idx, namespace="validation") -> STEP_OUTPUT:
702
+ """
703
+ Perform a single validation step.
704
+
705
+ This function processes the input batch, encodes frames, generates predictions using a sliding window approach,
706
+ and handles condition similarity logic for sampling. The results are decoded and stored for evaluation.
707
+
708
+ Args:
709
+ batch: Input batch of data containing frames, conditions, poses, etc.
710
+ batch_idx: Index of the current batch.
711
+ namespace: Namespace for logging (default: "validation").
712
+
713
+ Returns:
714
+ None: Appends the predicted and ground truth frames to `self.validation_step_outputs`.
715
+ """
716
+ # Preprocess the input batch
717
+ memory_condition_length = self.memory_condition_length
718
+ xs_raw, conditions, pose_conditions, c2w_mat, frame_idx = self._preprocess_batch(batch)
719
+
720
+
721
+ # Encode frames in chunks if necessary
722
+ total_frame = xs_raw.shape[0]
723
+ if total_frame > 10:
724
+ xs = torch.cat([
725
+ self.encode(xs_raw[int(total_frame * i / 10):int(total_frame * (i + 1) / 10)]).cpu()
726
+ for i in range(10)
727
+ ])
728
+ else:
729
+ xs = self.encode(xs_raw).cpu()
730
+
731
+ n_frames, batch_size, *_ = xs.shape
732
+ curr_frame = 0
733
+
734
+ # Initialize context frames
735
+ n_context_frames = self.context_frames // self.frame_stack
736
+ xs_pred = xs[:n_context_frames].clone()
737
+ curr_frame += n_context_frames
738
+
739
+ # Progress bar for sampling
740
+ pbar = tqdm(total=n_frames, initial=curr_frame, desc="Sampling")
741
+
742
+ while curr_frame < n_frames:
743
+ # Determine the horizon for the current chunk
744
+ horizon = min(n_frames - curr_frame, self.chunk_size) if self.chunk_size > 0 else n_frames - curr_frame
745
+ assert horizon <= self.n_tokens, "Horizon exceeds the number of tokens."
746
+
747
+ # Generate scheduling matrix and initialize noise
748
+ scheduling_matrix = self._generate_scheduling_matrix(horizon)
749
+ chunk = torch.randn((horizon, batch_size, *xs_pred.shape[2:]))
750
+ chunk = torch.clamp(chunk, -self.clip_noise, self.clip_noise).to(xs_pred.device)
751
+ xs_pred = torch.cat([xs_pred, chunk], 0)
752
+
753
+ # Sliding window: only input the last `n_tokens` frames
754
+ start_frame = max(0, curr_frame + horizon - self.n_tokens)
755
+ pbar.set_postfix({"start": start_frame, "end": curr_frame + horizon})
756
+
757
+ # Handle condition similarity logic
758
+ if memory_condition_length:
759
+ random_idx = self._generate_condition_indices(
760
+ curr_frame, memory_condition_length, xs_pred, pose_conditions, frame_idx, horizon
761
+ )
762
+
763
+ xs_pred = torch.cat([xs_pred, xs_pred[random_idx[:, range(xs_pred.shape[1])], range(xs_pred.shape[1])].clone()], 0)
764
+
765
+ # Prepare input conditions and pose conditions
766
+ input_condition, input_pose_condition, frame_idx_list = self._prepare_conditions(
767
+ start_frame, curr_frame, horizon, conditions, pose_conditions, c2w_mat, frame_idx, random_idx,
768
+ image_width=xs_raw.shape[-1], image_height=xs_raw.shape[-2]
769
+ )
770
+
771
+ # Perform sampling for each step in the scheduling matrix
772
+ for m in range(scheduling_matrix.shape[0] - 1):
773
+ from_noise_levels, to_noise_levels = self._prepare_noise_levels(
774
+ scheduling_matrix, m, curr_frame, batch_size, memory_condition_length
775
+ )
776
+
777
+ xs_pred[start_frame:] = self.diffusion_model.sample_step(
778
+ xs_pred[start_frame:].to(input_condition.device),
779
+ input_condition,
780
+ input_pose_condition,
781
+ from_noise_levels[start_frame:],
782
+ to_noise_levels[start_frame:],
783
+ current_frame=curr_frame,
784
+ mode="validation",
785
+ reference_length=memory_condition_length,
786
+ frame_idx=frame_idx_list
787
+ ).cpu()
788
+
789
+ # Remove condition similarity frames if applicable
790
+ if memory_condition_length:
791
+ xs_pred = xs_pred[:-memory_condition_length]
792
+
793
+ curr_frame += horizon
794
+ pbar.update(horizon)
795
+
796
+ # Decode predictions and ground truth
797
+ xs_pred = self.decode(xs_pred[n_context_frames:].to(conditions.device))
798
+ xs_decode = self.decode(xs[n_context_frames:].to(conditions.device))
799
+
800
+ if self.logger and self.log_video:
801
+ log_video(
802
+ xs_pred,
803
+ xs_decode,
804
+ step=batch_idx,
805
+ namespace=namespace + "_vis",
806
+ context_frames=self.context_frames,
807
+ logger=self.logger.experiment,
808
+ save_local=self.save_local,
809
+ local_save_dir=self.local_save_dir,
810
+ )
811
+
812
+ self._update_metric_accumulators(xs_pred, xs_decode)
813
+ return
814
+
815
+ def test_step(self, batch, batch_idx) -> STEP_OUTPUT:
816
+ return self.validation_step(batch, batch_idx, namespace="test")
817
+
818
+ @torch.no_grad()
819
+ def interactive(self, first_frame, new_actions, first_pose, device,
820
+ memory_latent_frames, memory_actions, memory_poses, memory_c2w, memory_frame_idx):
821
+
822
+ memory_condition_length = self.memory_condition_length
823
+
824
+ if memory_latent_frames is None:
825
+ first_frame = torch.from_numpy(first_frame)
826
+ new_actions = torch.from_numpy(new_actions)
827
+ first_pose = torch.from_numpy(first_pose)
828
+ first_frame_encode = self.encode(first_frame[None, None].to(device))
829
+ memory_latent_frames = first_frame_encode.cpu()
830
+ memory_actions = new_actions[None, None].to(device)
831
+ memory_poses = first_pose[None, None].to(device)
832
+ new_c2w_mat = euler_to_camera_to_world_matrix(first_pose)
833
+ memory_c2w = new_c2w_mat[None, None].to(device)
834
+ memory_frame_idx = torch.tensor([[0]]).to(device)
835
+ return first_frame.cpu().numpy(), memory_latent_frames.cpu().numpy(), memory_actions.cpu().numpy(), memory_poses.cpu().numpy(), memory_c2w.cpu().numpy(), memory_frame_idx.cpu().numpy()
836
+ else:
837
+ memory_latent_frames = torch.from_numpy(memory_latent_frames)
838
+ memory_actions = torch.from_numpy(memory_actions).to(device)
839
+ memory_poses = torch.from_numpy(memory_poses).to(device)
840
+ memory_c2w = torch.from_numpy(memory_c2w).to(device)
841
+ memory_frame_idx = torch.from_numpy(memory_frame_idx).to(device)
842
+ new_actions = new_actions.to(device)
843
+
844
+ curr_frame = 0
845
+ batch_size = 1
846
+ horizon = self.next_frame_length
847
+ n_frames = curr_frame + horizon
848
+ # context
849
+ n_context_frames = len(memory_latent_frames)
850
+ xs_pred = memory_latent_frames[:n_context_frames].clone()
851
+ curr_frame += n_context_frames
852
+
853
+ pbar = tqdm(total=n_frames, initial=curr_frame, desc="Sampling")
854
+
855
+ new_pose_condition_list = []
856
+ last_frame = xs_pred[-1].clone()
857
+ last_pose_condition = memory_poses[-1].clone()
858
+ curr_actions = new_actions.clone()
859
+ for hi in range(len(new_actions)):
860
+ last_pose_condition[:,3:] = last_pose_condition[:,3:] // 15
861
+ new_pose_condition_offset = self.pose_prediction_model(last_frame.to(device), curr_actions[None, hi], last_pose_condition)
862
+ new_pose_condition_offset[:,3:] = torch.round(new_pose_condition_offset[:,3:])
863
+ new_pose_condition = last_pose_condition + new_pose_condition_offset
864
+ new_pose_condition[:,3:] = new_pose_condition[:,3:] * 15
865
+ new_pose_condition[:,3:] %= 360
866
+ last_pose_condition = new_pose_condition.clone()
867
+ new_pose_condition_list.append(new_pose_condition[None])
868
+ new_pose_condition_list = torch.cat(new_pose_condition_list, 0)
869
+
870
+ ai = 0
871
+ while ai < len(new_actions):
872
+ next_horizon = min(horizon, len(new_actions) - ai)
873
+ last_frame = xs_pred[-1].clone()
874
+ curr_actions = new_actions[ai:ai+next_horizon].clone()
875
+
876
+ new_pose_condition = new_pose_condition_list[ai:ai+next_horizon].clone()
877
+
878
+ new_c2w_mat = euler_to_camera_to_world_matrix(new_pose_condition)
879
+ memory_poses = torch.cat([memory_poses, new_pose_condition])
880
+ memory_actions = torch.cat([memory_actions, curr_actions[:, None]])
881
+ memory_c2w = torch.cat([memory_c2w, new_c2w_mat])
882
+ new_indices = memory_frame_idx[-1,0] + torch.arange(next_horizon, device=memory_frame_idx.device) + 1
883
+
884
+ memory_frame_idx = torch.cat([memory_frame_idx, new_indices[:, None]])
885
+
886
+ conditions = memory_actions.clone()
887
+ pose_conditions = memory_poses.clone()
888
+ c2w_mat = memory_c2w .clone()
889
+ frame_idx = memory_frame_idx.clone()
890
+
891
+ # generation on frame
892
+ scheduling_matrix = self._generate_scheduling_matrix(next_horizon)
893
+ chunk = torch.randn((next_horizon, batch_size, *xs_pred.shape[2:])).to(xs_pred.device)
894
+ chunk = torch.clamp(chunk, -self.clip_noise, self.clip_noise)
895
+
896
+ xs_pred = torch.cat([xs_pred, chunk], 0)
897
+
898
+ # sliding window: only input the last n_tokens frames
899
+ start_frame = max(0, curr_frame - self.n_tokens)
900
+
901
+ pbar.set_postfix(
902
+ {
903
+ "start": start_frame,
904
+ "end": curr_frame + next_horizon,
905
+ }
906
+ )
907
+
908
+ # Handle condition similarity logic
909
+ if memory_condition_length:
910
+ random_idx = self._generate_condition_indices(
911
+ curr_frame, memory_condition_length, xs_pred, pose_conditions, frame_idx, next_horizon
912
+ )
913
+
914
+ # random_idx = np.unique(random_idx)[:, None]
915
+ # memory_condition_length = len(random_idx)
916
+ xs_pred = torch.cat([xs_pred, xs_pred[random_idx[:, range(xs_pred.shape[1])], range(xs_pred.shape[1])].clone()], 0)
917
+
918
+ # Prepare input conditions and pose conditions
919
+ input_condition, input_pose_condition, frame_idx_list = self._prepare_conditions(
920
+ start_frame, curr_frame, next_horizon, conditions, pose_conditions, c2w_mat, frame_idx, random_idx,
921
+ image_width=first_frame.shape[-1], image_height=first_frame.shape[-2]
922
+ )
923
+
924
+ # Perform sampling for each step in the scheduling matrix
925
+ for m in range(scheduling_matrix.shape[0] - 1):
926
+ from_noise_levels, to_noise_levels = self._prepare_noise_levels(
927
+ scheduling_matrix, m, curr_frame, batch_size, memory_condition_length
928
+ )
929
+
930
+ xs_pred[start_frame:] = self.diffusion_model.sample_step(
931
+ xs_pred[start_frame:].to(input_condition.device),
932
+ input_condition,
933
+ input_pose_condition,
934
+ from_noise_levels[start_frame:],
935
+ to_noise_levels[start_frame:],
936
+ current_frame=curr_frame,
937
+ mode="validation",
938
+ reference_length=memory_condition_length,
939
+ frame_idx=frame_idx_list
940
+ ).cpu()
941
+
942
+
943
+ if memory_condition_length:
944
+ xs_pred = xs_pred[:-memory_condition_length]
945
+
946
+ curr_frame += next_horizon
947
+ pbar.update(next_horizon)
948
+ ai += next_horizon
949
+
950
+ memory_latent_frames = torch.cat([memory_latent_frames, xs_pred[n_context_frames:]])
951
+ xs_pred = self.decode(xs_pred[n_context_frames:].to(device)).cpu()
952
+
953
+ return xs_pred.cpu().numpy(), memory_latent_frames.cpu().numpy(), memory_actions.cpu().numpy(), \
954
+ memory_poses.cpu().numpy(), memory_c2w.cpu().numpy(), memory_frame_idx.cpu().numpy()
algorithms/dememwm/models/attention.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Based on https://github.com/buoyancy99/diffusion-forcing/blob/main/algorithms/diffusion_forcing/models/attention.py
3
+ """
4
+
5
+ from typing import Optional
6
+ from collections import namedtuple
7
+ import torch
8
+ from torch import nn
9
+ from torch.nn import functional as F
10
+ from einops import rearrange
11
+ from .rotary_embedding_torch import RotaryEmbedding, apply_rotary_emb
12
+ import numpy as np
13
+
14
+ class TemporalAxialAttention(nn.Module):
15
+ def __init__(
16
+ self,
17
+ dim: int,
18
+ heads: int,
19
+ dim_head: int,
20
+ reference_length: int,
21
+ rotary_emb: RotaryEmbedding,
22
+ is_causal: bool = True,
23
+ is_temporal_independent: bool = False,
24
+ use_domain_adapter = False
25
+ ):
26
+ super().__init__()
27
+ self.inner_dim = dim_head * heads
28
+ self.heads = heads
29
+ self.head_dim = dim_head
30
+ self.inner_dim = dim_head * heads
31
+ self.to_qkv = nn.Linear(dim, self.inner_dim * 3, bias=False)
32
+
33
+ self.use_domain_adapter = use_domain_adapter
34
+ if self.use_domain_adapter:
35
+ lora_rank = 8
36
+ self.lora_A = nn.Linear(dim, lora_rank, bias=False)
37
+ self.lora_B = nn.Linear(lora_rank, self.inner_dim * 3, bias=False)
38
+
39
+ self.to_out = nn.Linear(self.inner_dim, dim)
40
+
41
+ self.rotary_emb = rotary_emb
42
+ self.is_causal = is_causal
43
+ self.is_temporal_independent = is_temporal_independent
44
+
45
+ self.reference_length = reference_length
46
+
47
+ def forward(self, x: torch.Tensor):
48
+ B, T, H, W, D = x.shape
49
+
50
+ # if T>=9:
51
+ # try:
52
+ # # x = torch.cat([x[:,:-1],x[:,16-T:17-T],x[:,-1:]], dim=1)
53
+ # x = torch.cat([x[:,16-T:17-T],x], dim=1)
54
+ # except:
55
+ # import pdb;pdb.set_trace()
56
+ # print("="*50)
57
+ # print(x.shape)
58
+
59
+ B, T, H, W, D = x.shape
60
+
61
+ q, k, v = self.to_qkv(x).chunk(3, dim=-1)
62
+
63
+ if self.use_domain_adapter:
64
+ q_lora, k_lora, v_lora = self.lora_B(self.lora_A(x)).chunk(3, dim=-1)
65
+ q = q+q_lora
66
+ k = k+k_lora
67
+ v = v+v_lora
68
+
69
+ q = rearrange(q, "B T H W (h d) -> (B H W) h T d", h=self.heads)
70
+ k = rearrange(k, "B T H W (h d) -> (B H W) h T d", h=self.heads)
71
+ v = rearrange(v, "B T H W (h d) -> (B H W) h T d", h=self.heads)
72
+
73
+ q = self.rotary_emb.rotate_queries_or_keys(q, self.rotary_emb.freqs)
74
+ k = self.rotary_emb.rotate_queries_or_keys(k, self.rotary_emb.freqs)
75
+
76
+ q, k, v = map(lambda t: t.contiguous(), (q, k, v))
77
+
78
+ if self.is_temporal_independent:
79
+ attn_bias = torch.ones((T, T), dtype=q.dtype, device=q.device)
80
+ attn_bias = attn_bias.masked_fill(attn_bias == 1, float('-inf'))
81
+ attn_bias[range(T), range(T)] = 0
82
+ elif self.is_causal:
83
+ attn_bias = torch.triu(torch.ones((T, T), dtype=q.dtype, device=q.device), diagonal=1)
84
+ attn_bias = attn_bias.masked_fill(attn_bias == 1, float('-inf'))
85
+ attn_bias[(T-self.reference_length):] = float('-inf')
86
+ attn_bias[range(T), range(T)] = 0
87
+ else:
88
+ attn_bias = None
89
+
90
+ try:
91
+ x = F.scaled_dot_product_attention(query=q, key=k, value=v, attn_mask=attn_bias)
92
+ except:
93
+ import pdb;pdb.set_trace()
94
+
95
+ x = rearrange(x, "(B H W) h T d -> B T H W (h d)", B=B, H=H, W=W)
96
+ x = x.to(q.dtype)
97
+
98
+ # linear proj
99
+ x = self.to_out(x)
100
+
101
+ # if T>=10:
102
+ # try:
103
+ # # x = torch.cat([x[:,:-2],x[:,-1:]], dim=1)
104
+ # x = x[:,1:]
105
+ # except:
106
+ # import pdb;pdb.set_trace()
107
+ # print(x.shape)
108
+ return x
109
+
110
+ class SpatialAxialAttention(nn.Module):
111
+ def __init__(
112
+ self,
113
+ dim: int,
114
+ heads: int,
115
+ dim_head: int,
116
+ rotary_emb: RotaryEmbedding,
117
+ use_domain_adapter = False
118
+ ):
119
+ super().__init__()
120
+ self.inner_dim = dim_head * heads
121
+ self.heads = heads
122
+ self.head_dim = dim_head
123
+ self.inner_dim = dim_head * heads
124
+ self.to_qkv = nn.Linear(dim, self.inner_dim * 3, bias=False)
125
+ self.use_domain_adapter = use_domain_adapter
126
+ if self.use_domain_adapter:
127
+ lora_rank = 8
128
+ self.lora_A = nn.Linear(dim, lora_rank, bias=False)
129
+ self.lora_B = nn.Linear(lora_rank, self.inner_dim * 3, bias=False)
130
+
131
+ self.to_out = nn.Linear(self.inner_dim, dim)
132
+
133
+ self.rotary_emb = rotary_emb
134
+
135
+ def forward(self, x: torch.Tensor):
136
+ B, T, H, W, D = x.shape
137
+
138
+ q, k, v = self.to_qkv(x).chunk(3, dim=-1)
139
+
140
+ if self.use_domain_adapter:
141
+ q_lora, k_lora, v_lora = self.lora_B(self.lora_A(x)).chunk(3, dim=-1)
142
+ q = q+q_lora
143
+ k = k+k_lora
144
+ v = v+v_lora
145
+
146
+ q = rearrange(q, "B T H W (h d) -> (B T) h H W d", h=self.heads)
147
+ k = rearrange(k, "B T H W (h d) -> (B T) h H W d", h=self.heads)
148
+ v = rearrange(v, "B T H W (h d) -> (B T) h H W d", h=self.heads)
149
+
150
+ freqs = self.rotary_emb.get_axial_freqs(H, W)
151
+ q = apply_rotary_emb(freqs, q)
152
+ k = apply_rotary_emb(freqs, k)
153
+
154
+ # prepare for attn
155
+ q = rearrange(q, "(B T) h H W d -> (B T) h (H W) d", B=B, T=T, h=self.heads)
156
+ k = rearrange(k, "(B T) h H W d -> (B T) h (H W) d", B=B, T=T, h=self.heads)
157
+ v = rearrange(v, "(B T) h H W d -> (B T) h (H W) d", B=B, T=T, h=self.heads)
158
+
159
+ x = F.scaled_dot_product_attention(query=q, key=k, value=v, is_causal=False)
160
+
161
+ x = rearrange(x, "(B T) h (H W) d -> B T H W (h d)", B=B, H=H, W=W)
162
+ x = x.to(q.dtype)
163
+
164
+ # linear proj
165
+ x = self.to_out(x)
166
+ return x
167
+
168
+ class MemTemporalAxialAttention(nn.Module):
169
+ def __init__(
170
+ self,
171
+ dim: int,
172
+ heads: int,
173
+ dim_head: int,
174
+ rotary_emb: RotaryEmbedding,
175
+ is_causal: bool = True,
176
+ ):
177
+ super().__init__()
178
+ self.inner_dim = dim_head * heads
179
+ self.heads = heads
180
+ self.head_dim = dim_head
181
+ self.inner_dim = dim_head * heads
182
+ self.to_qkv = nn.Linear(dim, self.inner_dim * 3, bias=False)
183
+ self.to_out = nn.Linear(self.inner_dim, dim)
184
+
185
+ self.rotary_emb = rotary_emb
186
+ self.is_causal = is_causal
187
+
188
+ self.reference_length = 3
189
+
190
+ def forward(self, x: torch.Tensor):
191
+ B, T, H, W, D = x.shape
192
+
193
+ q, k, v = self.to_qkv(x).chunk(3, dim=-1)
194
+
195
+
196
+ q = rearrange(q, "B T H W (h d) -> (B H W) h T d", h=self.heads)
197
+ k = rearrange(k, "B T H W (h d) -> (B H W) h T d", h=self.heads)
198
+ v = rearrange(v, "B T H W (h d) -> (B H W) h T d", h=self.heads)
199
+
200
+
201
+
202
+ # q = self.rotary_emb.rotate_queries_or_keys(q, self.rotary_emb.freqs)
203
+ # k = self.rotary_emb.rotate_queries_or_keys(k, self.rotary_emb.freqs)
204
+
205
+ q, k, v = map(lambda t: t.contiguous(), (q, k, v))
206
+
207
+ # if T == 21000:
208
+ # # 手动计算缩放点积分数
209
+ # _, _, _, d_k = q.shape
210
+ # scores = torch.einsum("b h n d, b h m d -> b h n m", q, k) / (d_k ** 0.5) # Shape: (B, T_q, T_k)
211
+
212
+ # # 计算注意力图 (Attention Map)
213
+ # attention_map = F.softmax(scores, dim=-1) # Shape: (B, T_q, T_k)
214
+ # b_, h_, n_, m_ = attention_map.shape
215
+ # attention_map = attention_map.reshape(1, int(np.sqrt(b_/1)), int(np.sqrt(b_/1)), h_, n_, m_)
216
+ # attention_map = attention_map.mean(3)
217
+
218
+ # attn_bias = torch.zeros((T, T), dtype=q.dtype, device=q.device)
219
+ # T_origin = T - self.reference_length
220
+ # attn_bias[:T_origin, T_origin:] = 1
221
+ # attn_bias[range(T), range(T)] = 1
222
+
223
+ # attention_map = attention_map * attn_bias
224
+
225
+ # # print 注意力图
226
+ # import matplotlib.pyplot as plt
227
+ # fig, axes = plt.subplots(21000, 21000, figsize=(9, 9)) # 调整figsize以适配图像大小
228
+
229
+ # # 遍历3*3维度
230
+ # for i in range(21000):
231
+ # for j in range(21000):
232
+ # # 取出第(i, j)个子图像
233
+ # img = attention_map[0, :, :, i, j].cpu().numpy()
234
+ # axes[i, j].imshow(img, cmap='viridis') # 可以自定义cmap
235
+ # axes[i, j].axis('off') # 隐藏坐标轴
236
+
237
+ # # 调整子图间距
238
+ # plt.tight_layout()
239
+ # plt.savefig('attention_map.png')
240
+ # import pdb; pdb.set_trace()
241
+ # plt.close()
242
+
243
+ attn_bias = torch.zeros((T, T), dtype=q.dtype, device=q.device)
244
+ attn_bias = attn_bias.masked_fill(attn_bias == 0, float('-inf'))
245
+ T_origin = T - self.reference_length
246
+ attn_bias[:T_origin, T_origin:] = 0
247
+ attn_bias[range(T), range(T)] = 0
248
+
249
+ # if T==121000:
250
+ # import pdb;pdb.set_trace()
251
+
252
+ try:
253
+ x = F.scaled_dot_product_attention(query=q, key=k, value=v, attn_mask=attn_bias)
254
+ except:
255
+ import pdb;pdb.set_trace()
256
+
257
+ x = rearrange(x, "(B H W) h T d -> B T H W (h d)", B=B, H=H, W=W)
258
+ x = x.to(q.dtype)
259
+
260
+ # linear proj
261
+ x = self.to_out(x)
262
+ return x
263
+
264
+ class MemFullAttention(nn.Module):
265
+ def __init__(
266
+ self,
267
+ dim: int,
268
+ heads: int,
269
+ dim_head: int,
270
+ reference_length: int,
271
+ rotary_emb: RotaryEmbedding,
272
+ is_causal: bool = True
273
+ ):
274
+ super().__init__()
275
+ self.inner_dim = dim_head * heads
276
+ self.heads = heads
277
+ self.head_dim = dim_head
278
+ self.inner_dim = dim_head * heads
279
+ self.to_qkv = nn.Linear(dim, self.inner_dim * 3, bias=False)
280
+ self.to_out = nn.Linear(self.inner_dim, dim)
281
+
282
+ self.rotary_emb = rotary_emb
283
+ self.is_causal = is_causal
284
+
285
+ self.reference_length = reference_length
286
+
287
+ self.store = None
288
+
289
+ def forward(self, x: torch.Tensor, relative_embedding=False,
290
+ extra_condition=None,
291
+ state_embed_only_on_qk=False,
292
+ reference_length=None):
293
+
294
+ B, T, H, W, D = x.shape
295
+
296
+ if state_embed_only_on_qk:
297
+ q, k, _ = self.to_qkv(x+extra_condition).chunk(3, dim=-1)
298
+ _, _, v = self.to_qkv(x).chunk(3, dim=-1)
299
+ else:
300
+ q, k, v = self.to_qkv(x).chunk(3, dim=-1)
301
+
302
+ if relative_embedding:
303
+ length = reference_length+1
304
+ n_frames = T // length
305
+ x = x.reshape(B, n_frames, length, H, W, D)
306
+
307
+ x_list = []
308
+
309
+ for i in range(n_frames):
310
+ if i == n_frames-1:
311
+ q_i = rearrange(q[:, i*length:], "B T H W (h d) -> B h (T H W) d", h=self.heads)
312
+ k_i = rearrange(k[:, i*length+1:(i+1)*length], "B T H W (h d) -> B h (T H W) d", h=self.heads)
313
+ v_i = rearrange(v[:, i*length+1:(i+1)*length], "B T H W (h d) -> B h (T H W) d", h=self.heads)
314
+ else:
315
+ q_i = rearrange(q[:, i*length:i*length+1], "B T H W (h d) -> B h (T H W) d", h=self.heads)
316
+ k_i = rearrange(k[:, i*length+1:(i+1)*length], "B T H W (h d) -> B h (T H W) d", h=self.heads)
317
+ v_i = rearrange(v[:, i*length+1:(i+1)*length], "B T H W (h d) -> B h (T H W) d", h=self.heads)
318
+
319
+ q_i, k_i, v_i = map(lambda t: t.contiguous(), (q_i, k_i, v_i))
320
+ x_i = F.scaled_dot_product_attention(query=q_i, key=k_i, value=v_i)
321
+ x_i = rearrange(x_i, "B h (T H W) d -> B T H W (h d)", B=B, H=H, W=W)
322
+ x_i = x_i.to(q.dtype)
323
+ x_list.append(x_i)
324
+
325
+ x = torch.cat(x_list, dim=1)
326
+
327
+
328
+ else:
329
+ T_ = T - reference_length
330
+ q = rearrange(q, "B T H W (h d) -> B h (T H W) d", h=self.heads)
331
+ k = rearrange(k[:, T_:], "B T H W (h d) -> B h (T H W) d", h=self.heads)
332
+ v = rearrange(v[:, T_:], "B T H W (h d) -> B h (T H W) d", h=self.heads)
333
+
334
+ q, k, v = map(lambda t: t.contiguous(), (q, k, v))
335
+ x = F.scaled_dot_product_attention(query=q, key=k, value=v)
336
+ x = rearrange(x, "B h (T H W) d -> B T H W (h d)", B=B, H=H, W=W)
337
+ x = x.to(q.dtype)
338
+
339
+ # linear proj
340
+ x = self.to_out(x)
341
+
342
+ return x
algorithms/dememwm/models/cameractrl_module.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ class SimpleCameraPoseEncoder(nn.Module):
3
+ def __init__(self, c_in, c_out, hidden_dim=128):
4
+ super(SimpleCameraPoseEncoder, self).__init__()
5
+ self.model = nn.Sequential(
6
+ nn.Linear(c_in, hidden_dim),
7
+ nn.ReLU(),
8
+ nn.Linear(hidden_dim, c_out)
9
+ )
10
+ def forward(self, x):
11
+ return self.model(x)
12
+
algorithms/dememwm/models/diffusion.py ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional, Callable
2
+ from collections import namedtuple
3
+ from omegaconf import DictConfig
4
+ import torch
5
+ from torch import nn
6
+ from torch.nn import functional as F
7
+ from einops import rearrange
8
+ from .utils import linear_beta_schedule, cosine_beta_schedule, sigmoid_beta_schedule, extract
9
+ from .dit import DiT_models
10
+
11
+ ModelPrediction = namedtuple("ModelPrediction", ["pred_noise", "pred_x_start", "model_out"])
12
+
13
+
14
+ class Diffusion(nn.Module):
15
+ # Special thanks to lucidrains for the implementation of the base Diffusion model
16
+ # https://github.com/lucidrains/denoising-diffusion-pytorch
17
+
18
+ def __init__(
19
+ self,
20
+ x_shape: torch.Size,
21
+ reference_length: int,
22
+ action_cond_dim: int,
23
+ pose_cond_dim,
24
+ is_causal: bool,
25
+ cfg: DictConfig,
26
+ is_dit: bool=False,
27
+ use_plucker=False,
28
+ relative_embedding=False,
29
+ state_embed_only_on_qk=False,
30
+ use_memory_attention=False,
31
+ add_timestamp_embedding=False,
32
+ ref_mode='sequential'
33
+ ):
34
+ super().__init__()
35
+ self.cfg = cfg
36
+
37
+ self.x_shape = x_shape
38
+ self.action_cond_dim = action_cond_dim
39
+ self.timesteps = cfg.timesteps
40
+ self.sampling_timesteps = cfg.sampling_timesteps
41
+ self.beta_schedule = cfg.beta_schedule
42
+ self.schedule_fn_kwargs = cfg.schedule_fn_kwargs
43
+ self.objective = cfg.objective
44
+ self.use_fused_snr = cfg.use_fused_snr
45
+ self.snr_clip = cfg.snr_clip
46
+ self.cum_snr_decay = cfg.cum_snr_decay
47
+ self.ddim_sampling_eta = cfg.ddim_sampling_eta
48
+ self.clip_noise = cfg.clip_noise
49
+ self.arch = cfg.architecture
50
+ self.stabilization_level = cfg.stabilization_level
51
+ self.is_causal = is_causal
52
+ self.is_dit = is_dit
53
+ self.reference_length = reference_length
54
+ self.pose_cond_dim = pose_cond_dim
55
+ self.use_plucker = use_plucker
56
+ self.relative_embedding = relative_embedding
57
+ self.state_embed_only_on_qk = state_embed_only_on_qk
58
+ self.use_memory_attention = use_memory_attention
59
+ self.add_timestamp_embedding = add_timestamp_embedding
60
+ self.ref_mode = ref_mode
61
+
62
+ self._build_model()
63
+ self._build_buffer()
64
+
65
+ def _build_model(self):
66
+ x_channel = self.x_shape[0]
67
+ if self.is_dit:
68
+ self.model = DiT_models["DiT-S/2"](action_cond_dim=self.action_cond_dim,
69
+ pose_cond_dim=self.pose_cond_dim, reference_length=self.reference_length,
70
+ use_plucker=self.use_plucker,
71
+ relative_embedding=self.relative_embedding,
72
+ state_embed_only_on_qk=self.state_embed_only_on_qk,
73
+ use_memory_attention=self.use_memory_attention,
74
+ add_timestamp_embedding=self.add_timestamp_embedding,
75
+ ref_mode=self.ref_mode)
76
+ else:
77
+ raise NotImplementedError
78
+
79
+ def _build_buffer(self):
80
+ if self.beta_schedule == "linear":
81
+ beta_schedule_fn = linear_beta_schedule
82
+ elif self.beta_schedule == "cosine":
83
+ beta_schedule_fn = cosine_beta_schedule
84
+ elif self.beta_schedule == "sigmoid":
85
+ beta_schedule_fn = sigmoid_beta_schedule
86
+ else:
87
+ raise ValueError(f"unknown beta schedule {self.beta_schedule}")
88
+
89
+ betas = beta_schedule_fn(self.timesteps, **self.schedule_fn_kwargs)
90
+
91
+ alphas = 1.0 - betas
92
+ alphas_cumprod = torch.cumprod(alphas, dim=0)
93
+ alphas_cumprod_prev = F.pad(alphas_cumprod[:-1], (1, 0), value=1.0)
94
+
95
+ # sampling related parameters
96
+ assert self.sampling_timesteps <= self.timesteps
97
+ self.is_ddim_sampling = self.sampling_timesteps < self.timesteps
98
+
99
+ # helper function to register buffer from float64 to float32
100
+ register_buffer = lambda name, val: self.register_buffer(name, val.to(torch.float32))
101
+
102
+ register_buffer("betas", betas)
103
+ register_buffer("alphas_cumprod", alphas_cumprod)
104
+ register_buffer("alphas_cumprod_prev", alphas_cumprod_prev)
105
+
106
+ # calculations for diffusion q(x_t | x_{t-1}) and others
107
+
108
+ register_buffer("sqrt_alphas_cumprod", torch.sqrt(alphas_cumprod))
109
+ register_buffer("sqrt_one_minus_alphas_cumprod", torch.sqrt(1.0 - alphas_cumprod))
110
+ register_buffer("log_one_minus_alphas_cumprod", torch.log(1.0 - alphas_cumprod))
111
+ register_buffer("sqrt_recip_alphas_cumprod", torch.sqrt(1.0 / alphas_cumprod))
112
+ register_buffer("sqrt_recipm1_alphas_cumprod", torch.sqrt(1.0 / alphas_cumprod - 1))
113
+
114
+ # calculations for posterior q(x_{t-1} | x_t, x_0)
115
+
116
+ posterior_variance = betas * (1.0 - alphas_cumprod_prev) / (1.0 - alphas_cumprod)
117
+
118
+ # above: equal to 1. / (1. / (1. - alpha_cumprod_tm1) + alpha_t / beta_t)
119
+
120
+ register_buffer("posterior_variance", posterior_variance)
121
+
122
+ # below: log calculation clipped because the posterior variance is 0 at the beginning of the diffusion chain
123
+
124
+ register_buffer(
125
+ "posterior_log_variance_clipped",
126
+ torch.log(posterior_variance.clamp(min=1e-20)),
127
+ )
128
+ register_buffer(
129
+ "posterior_mean_coef1",
130
+ betas * torch.sqrt(alphas_cumprod_prev) / (1.0 - alphas_cumprod),
131
+ )
132
+ register_buffer(
133
+ "posterior_mean_coef2",
134
+ (1.0 - alphas_cumprod_prev) * torch.sqrt(alphas) / (1.0 - alphas_cumprod),
135
+ )
136
+
137
+ # calculate p2 reweighting
138
+
139
+ # register_buffer(
140
+ # "p2_loss_weight",
141
+ # (self.p2_loss_weight_k + alphas_cumprod / (1 - alphas_cumprod))
142
+ # ** -self.p2_loss_weight_gamma,
143
+ # )
144
+
145
+ # derive loss weight
146
+ # https://arxiv.org/abs/2303.09556
147
+ # snr: signal noise ratio
148
+ snr = alphas_cumprod / (1 - alphas_cumprod)
149
+ clipped_snr = snr.clone()
150
+ clipped_snr.clamp_(max=self.snr_clip)
151
+
152
+ register_buffer("clipped_snr", clipped_snr)
153
+ register_buffer("snr", snr)
154
+
155
+ def add_shape_channels(self, x):
156
+ return rearrange(x, f"... -> ...{' 1' * len(self.x_shape)}")
157
+
158
+ def model_predictions(self, x, t, action_cond=None, current_frame=None,
159
+ pose_cond=None, mode="training", reference_length=None, frame_idx=None):
160
+ x = x.permute(1,0,2,3,4)
161
+ action_cond = action_cond.permute(1,0,2)
162
+ if pose_cond is not None and pose_cond[0] is not None:
163
+ try:
164
+ pose_cond = pose_cond.permute(1,0,2)
165
+ except:
166
+ pass
167
+ t = t.permute(1,0)
168
+ model_output = self.model(x, t, action_cond, current_frame=current_frame, pose_cond=pose_cond,
169
+ mode=mode, reference_length=reference_length, frame_idx=frame_idx)
170
+ model_output = model_output.permute(1,0,2,3,4)
171
+ x = x.permute(1,0,2,3,4)
172
+ t = t.permute(1,0)
173
+
174
+ if self.objective == "pred_noise":
175
+ pred_noise = torch.clamp(model_output, -self.clip_noise, self.clip_noise)
176
+ x_start = self.predict_start_from_noise(x, t, pred_noise)
177
+
178
+ elif self.objective == "pred_x0":
179
+ x_start = model_output
180
+ pred_noise = self.predict_noise_from_start(x, t, x_start)
181
+
182
+ elif self.objective == "pred_v":
183
+ v = model_output
184
+ x_start = self.predict_start_from_v(x, t, v)
185
+ pred_noise = self.predict_noise_from_start(x, t, x_start)
186
+
187
+
188
+ return ModelPrediction(pred_noise, x_start, model_output)
189
+
190
+ def predict_start_from_noise(self, x_t, t, noise):
191
+ return (
192
+ extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t
193
+ - extract(self.sqrt_recipm1_alphas_cumprod, t, x_t.shape) * noise
194
+ )
195
+
196
+ def predict_noise_from_start(self, x_t, t, x0):
197
+ return (extract(self.sqrt_recip_alphas_cumprod, t, x_t.shape) * x_t - x0) / extract(
198
+ self.sqrt_recipm1_alphas_cumprod, t, x_t.shape
199
+ )
200
+
201
+ def predict_v(self, x_start, t, noise):
202
+ return (
203
+ extract(self.sqrt_alphas_cumprod, t, x_start.shape) * noise
204
+ - extract(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * x_start
205
+ )
206
+
207
+ def predict_start_from_v(self, x_t, t, v):
208
+ return (
209
+ extract(self.sqrt_alphas_cumprod, t, x_t.shape) * x_t
210
+ - extract(self.sqrt_one_minus_alphas_cumprod, t, x_t.shape) * v
211
+ )
212
+
213
+ def q_mean_variance(self, x_start, t):
214
+ mean = extract(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start
215
+ variance = extract(1.0 - self.alphas_cumprod, t, x_start.shape)
216
+ log_variance = extract(self.log_one_minus_alphas_cumprod, t, x_start.shape)
217
+ return mean, variance, log_variance
218
+
219
+ def q_posterior(self, x_start, x_t, t):
220
+ posterior_mean = (
221
+ extract(self.posterior_mean_coef1, t, x_t.shape) * x_start
222
+ + extract(self.posterior_mean_coef2, t, x_t.shape) * x_t
223
+ )
224
+ posterior_variance = extract(self.posterior_variance, t, x_t.shape)
225
+ posterior_log_variance_clipped = extract(self.posterior_log_variance_clipped, t, x_t.shape)
226
+ return posterior_mean, posterior_variance, posterior_log_variance_clipped
227
+
228
+ def q_sample(self, x_start, t, noise=None):
229
+ if noise is None:
230
+ noise = torch.randn_like(x_start)
231
+ noise = torch.clamp(noise, -self.clip_noise, self.clip_noise)
232
+ return (
233
+ extract(self.sqrt_alphas_cumprod, t, x_start.shape) * x_start
234
+ + extract(self.sqrt_one_minus_alphas_cumprod, t, x_start.shape) * noise
235
+ )
236
+
237
+ def p_mean_variance(self, x, t, action_cond=None, pose_cond=None, reference_length=None):
238
+ model_pred = self.model_predictions(x=x, t=t, action_cond=action_cond,
239
+ pose_cond=pose_cond, reference_length=reference_length,
240
+ frame_idx=frame_idx)
241
+ x_start = model_pred.pred_x_start
242
+ return self.q_posterior(x_start=x_start, x_t=x, t=t)
243
+
244
+ def compute_loss_weights(self, noise_levels: torch.Tensor):
245
+
246
+ snr = self.snr[noise_levels]
247
+ clipped_snr = self.clipped_snr[noise_levels]
248
+ normalized_clipped_snr = clipped_snr / self.snr_clip
249
+ normalized_snr = snr / self.snr_clip
250
+
251
+ if not self.use_fused_snr:
252
+ # min SNR reweighting
253
+ match self.objective:
254
+ case "pred_noise":
255
+ return clipped_snr / snr
256
+ case "pred_x0":
257
+ return clipped_snr
258
+ case "pred_v":
259
+ return clipped_snr / (snr + 1)
260
+
261
+ cum_snr = torch.zeros_like(normalized_snr)
262
+ for t in range(0, noise_levels.shape[0]):
263
+ if t == 0:
264
+ cum_snr[t] = normalized_clipped_snr[t]
265
+ else:
266
+ cum_snr[t] = self.cum_snr_decay * cum_snr[t - 1] + (1 - self.cum_snr_decay) * normalized_clipped_snr[t]
267
+
268
+ cum_snr = F.pad(cum_snr[:-1], (0, 0, 1, 0), value=0.0)
269
+ clipped_fused_snr = 1 - (1 - cum_snr * self.cum_snr_decay) * (1 - normalized_clipped_snr)
270
+ fused_snr = 1 - (1 - cum_snr * self.cum_snr_decay) * (1 - normalized_snr)
271
+
272
+ match self.objective:
273
+ case "pred_noise":
274
+ return clipped_fused_snr / fused_snr
275
+ case "pred_x0":
276
+ return clipped_fused_snr * self.snr_clip
277
+ case "pred_v":
278
+ return clipped_fused_snr * self.snr_clip / (fused_snr * self.snr_clip + 1)
279
+ case _:
280
+ raise ValueError(f"unknown objective {self.objective}")
281
+
282
+ def forward(
283
+ self,
284
+ x: torch.Tensor,
285
+ action_cond: Optional[torch.Tensor],
286
+ pose_cond,
287
+ noise_levels: torch.Tensor,
288
+ reference_length,
289
+ frame_idx=None
290
+ ):
291
+ noise = torch.randn_like(x)
292
+ noise = torch.clamp(noise, -self.clip_noise, self.clip_noise)
293
+
294
+ noised_x = self.q_sample(x_start=x, t=noise_levels, noise=noise)
295
+
296
+ model_pred = self.model_predictions(x=noised_x, t=noise_levels, action_cond=action_cond,
297
+ pose_cond=pose_cond,reference_length=reference_length, frame_idx=frame_idx)
298
+
299
+ pred = model_pred.model_out
300
+ x_pred = model_pred.pred_x_start
301
+
302
+ if self.objective == "pred_noise":
303
+ target = noise
304
+ elif self.objective == "pred_x0":
305
+ target = x
306
+ elif self.objective == "pred_v":
307
+ target = self.predict_v(x, noise_levels, noise)
308
+ else:
309
+ raise ValueError(f"unknown objective {self.objective}")
310
+
311
+ # 训练的时候每个frame随便给噪声
312
+ loss = F.mse_loss(pred, target.detach(), reduction="none")
313
+ loss_weight = self.compute_loss_weights(noise_levels)
314
+
315
+ loss_weight = loss_weight.view(*loss_weight.shape, *((1,) * (loss.ndim - 2)))
316
+
317
+ loss = loss * loss_weight
318
+
319
+ return x_pred, loss
320
+
321
+ def sample_step(
322
+ self,
323
+ x: torch.Tensor,
324
+ action_cond: Optional[torch.Tensor],
325
+ pose_cond,
326
+ curr_noise_level: torch.Tensor,
327
+ next_noise_level: torch.Tensor,
328
+ guidance_fn: Optional[Callable] = None,
329
+ current_frame=None,
330
+ mode="training",
331
+ reference_length=None,
332
+ frame_idx=None
333
+ ):
334
+ real_steps = torch.linspace(-1, self.timesteps - 1, steps=self.sampling_timesteps + 1, device=x.device).long()
335
+
336
+ # convert noise levels (0 ~ sampling_timesteps) to real noise levels (-1 ~ timesteps - 1)
337
+ curr_noise_level = real_steps[curr_noise_level]
338
+ next_noise_level = real_steps[next_noise_level]
339
+
340
+ if self.is_ddim_sampling:
341
+ return self.ddim_sample_step(
342
+ x=x,
343
+ action_cond=action_cond,
344
+ pose_cond=pose_cond,
345
+ curr_noise_level=curr_noise_level,
346
+ next_noise_level=next_noise_level,
347
+ guidance_fn=guidance_fn,
348
+ current_frame=current_frame,
349
+ mode=mode,
350
+ reference_length=reference_length,
351
+ frame_idx=frame_idx
352
+ )
353
+
354
+ # FIXME: temporary code for checking ddpm sampling
355
+ assert torch.all(
356
+ (curr_noise_level - 1 == next_noise_level) | ((curr_noise_level == -1) & (next_noise_level == -1))
357
+ ), "Wrong noise level given for ddpm sampling."
358
+
359
+ assert (
360
+ self.sampling_timesteps == self.timesteps
361
+ ), "sampling_timesteps should be equal to timesteps for ddpm sampling."
362
+
363
+ return self.ddpm_sample_step(
364
+ x=x,
365
+ action_cond=action_cond,
366
+ pose_cond=pose_cond,
367
+ curr_noise_level=curr_noise_level,
368
+ guidance_fn=guidance_fn,
369
+ reference_length=reference_length,
370
+ frame_idx=frame_idx
371
+ )
372
+
373
+ def ddpm_sample_step(
374
+ self,
375
+ x: torch.Tensor,
376
+ action_cond: Optional[torch.Tensor],
377
+ pose_cond,
378
+ curr_noise_level: torch.Tensor,
379
+ guidance_fn: Optional[Callable] = None,
380
+ reference_length=None,
381
+ frame_idx=None,
382
+ ):
383
+ clipped_curr_noise_level = torch.where(
384
+ curr_noise_level < 0,
385
+ torch.full_like(curr_noise_level, self.stabilization_level - 1, dtype=torch.long),
386
+ curr_noise_level,
387
+ )
388
+
389
+ # treating as stabilization would require us to scale with sqrt of alpha_cum
390
+ orig_x = x.clone().detach()
391
+ scaled_context = self.q_sample(
392
+ x,
393
+ clipped_curr_noise_level,
394
+ noise=torch.zeros_like(x),
395
+ )
396
+ x = torch.where(self.add_shape_channels(curr_noise_level < 0), scaled_context, orig_x)
397
+
398
+ if guidance_fn is not None:
399
+ raise NotImplementedError("Guidance function is not implemented for ddpm sampling yet.")
400
+
401
+ else:
402
+ model_mean, _, model_log_variance = self.p_mean_variance(
403
+ x=x,
404
+ t=clipped_curr_noise_level,
405
+ action_cond=action_cond,
406
+ pose_cond=pose_cond,
407
+ reference_length=reference_length,
408
+ frame_idx=frame_idx
409
+ )
410
+
411
+ noise = torch.where(
412
+ self.add_shape_channels(clipped_curr_noise_level > 0),
413
+ torch.randn_like(x),
414
+ 0,
415
+ )
416
+ noise = torch.clamp(noise, -self.clip_noise, self.clip_noise)
417
+ x_pred = model_mean + torch.exp(0.5 * model_log_variance) * noise
418
+
419
+ # only update frames where the noise level decreases
420
+ return torch.where(self.add_shape_channels(curr_noise_level == -1), orig_x, x_pred)
421
+
422
+ def ddim_sample_step(
423
+ self,
424
+ x: torch.Tensor,
425
+ action_cond: Optional[torch.Tensor],
426
+ pose_cond,
427
+ curr_noise_level: torch.Tensor,
428
+ next_noise_level: torch.Tensor,
429
+ guidance_fn: Optional[Callable] = None,
430
+ current_frame=None,
431
+ mode="training",
432
+ reference_length=None,
433
+ frame_idx=None
434
+ ):
435
+ # convert noise level -1 to self.stabilization_level - 1
436
+ clipped_curr_noise_level = torch.where(
437
+ curr_noise_level < 0,
438
+ torch.full_like(curr_noise_level, self.stabilization_level - 1, dtype=torch.long),
439
+ curr_noise_level,
440
+ )
441
+
442
+ # treating as stabilization would require us to scale with sqrt of alpha_cum
443
+ orig_x = x.clone().detach()
444
+ scaled_context = self.q_sample(
445
+ x,
446
+ clipped_curr_noise_level,
447
+ noise=torch.zeros_like(x),
448
+ )
449
+ x = torch.where(self.add_shape_channels(curr_noise_level < 0), scaled_context, orig_x)
450
+
451
+ alpha = self.alphas_cumprod[clipped_curr_noise_level]
452
+ alpha_next = torch.where(
453
+ next_noise_level < 0,
454
+ torch.ones_like(next_noise_level),
455
+ self.alphas_cumprod[next_noise_level],
456
+ )
457
+ sigma = torch.where(
458
+ next_noise_level < 0,
459
+ torch.zeros_like(next_noise_level),
460
+ self.ddim_sampling_eta * ((1 - alpha / alpha_next) * (1 - alpha_next) / (1 - alpha)).sqrt(),
461
+ )
462
+ c = (1 - alpha_next - sigma**2).sqrt()
463
+
464
+ alpha_next = self.add_shape_channels(alpha_next)
465
+ c = self.add_shape_channels(c)
466
+ sigma = self.add_shape_channels(sigma)
467
+
468
+ if guidance_fn is not None:
469
+ with torch.enable_grad():
470
+ x = x.detach().requires_grad_()
471
+
472
+ model_pred = self.model_predictions(
473
+ x=x,
474
+ t=clipped_curr_noise_level,
475
+ action_cond=action_cond,
476
+ pose_cond=pose_cond,
477
+ current_frame=current_frame,
478
+ mode=mode,
479
+ reference_length=reference_length,
480
+ frame_idx=frame_idx
481
+ )
482
+
483
+ guidance_loss = guidance_fn(model_pred.pred_x_start)
484
+ grad = -torch.autograd.grad(
485
+ guidance_loss,
486
+ x,
487
+ )[0]
488
+
489
+ pred_noise = model_pred.pred_noise + (1 - alpha_next).sqrt() * grad
490
+ x_start = self.predict_start_from_noise(x, clipped_curr_noise_level, pred_noise)
491
+
492
+ else:
493
+ # print(clipped_curr_noise_level)
494
+ model_pred = self.model_predictions(
495
+ x=x,
496
+ t=clipped_curr_noise_level,
497
+ action_cond=action_cond,
498
+ pose_cond=pose_cond,
499
+ current_frame=current_frame,
500
+ mode=mode,
501
+ reference_length=reference_length,
502
+ frame_idx=frame_idx
503
+ )
504
+ x_start = model_pred.pred_x_start
505
+ pred_noise = model_pred.pred_noise
506
+
507
+ noise = torch.randn_like(x)
508
+ noise = torch.clamp(noise, -self.clip_noise, self.clip_noise)
509
+
510
+ x_pred = x_start * alpha_next.sqrt() + pred_noise * c + sigma * noise
511
+
512
+ # only update frames where the noise level decreases
513
+ mask = curr_noise_level == next_noise_level
514
+ x_pred = torch.where(
515
+ self.add_shape_channels(mask),
516
+ orig_x,
517
+ x_pred,
518
+ )
519
+
520
+ return x_pred
algorithms/dememwm/models/dit.py ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ References:
3
+ - DiT: https://github.com/facebookresearch/DiT/blob/main/models.py
4
+ - Diffusion Forcing: https://github.com/buoyancy99/diffusion-forcing/blob/main/algorithms/diffusion_forcing/models/unet3d.py
5
+ - Latte: https://github.com/Vchitect/Latte/blob/main/models/latte.py
6
+ """
7
+
8
+ from typing import Optional, Literal
9
+ import torch
10
+ from torch import nn
11
+ from .rotary_embedding_torch import RotaryEmbedding
12
+ from einops import rearrange
13
+ from .attention import SpatialAxialAttention, TemporalAxialAttention, MemTemporalAxialAttention, MemFullAttention
14
+ from timm.models.vision_transformer import Mlp
15
+ from timm.layers.helpers import to_2tuple
16
+ import math
17
+ from collections import namedtuple
18
+ from typing import Optional, Callable
19
+ from .cameractrl_module import SimpleCameraPoseEncoder
20
+
21
+ def modulate(x, shift, scale):
22
+ fixed_dims = [1] * len(shift.shape[1:])
23
+ shift = shift.repeat(x.shape[0] // shift.shape[0], *fixed_dims)
24
+ scale = scale.repeat(x.shape[0] // scale.shape[0], *fixed_dims)
25
+ while shift.dim() < x.dim():
26
+ shift = shift.unsqueeze(-2)
27
+ scale = scale.unsqueeze(-2)
28
+ return x * (1 + scale) + shift
29
+
30
+ def gate(x, g):
31
+ fixed_dims = [1] * len(g.shape[1:])
32
+ g = g.repeat(x.shape[0] // g.shape[0], *fixed_dims)
33
+ while g.dim() < x.dim():
34
+ g = g.unsqueeze(-2)
35
+ return g * x
36
+
37
+
38
+ class PatchEmbed(nn.Module):
39
+ """2D Image to Patch Embedding"""
40
+
41
+ def __init__(
42
+ self,
43
+ img_height=256,
44
+ img_width=256,
45
+ patch_size=16,
46
+ in_chans=3,
47
+ embed_dim=768,
48
+ norm_layer=None,
49
+ flatten=True,
50
+ ):
51
+ super().__init__()
52
+ img_size = (img_height, img_width)
53
+ patch_size = to_2tuple(patch_size)
54
+ self.img_size = img_size
55
+ self.patch_size = patch_size
56
+ self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
57
+ self.num_patches = self.grid_size[0] * self.grid_size[1]
58
+ self.flatten = flatten
59
+
60
+ self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
61
+ self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
62
+
63
+ def forward(self, x, random_sample=False):
64
+ B, C, H, W = x.shape
65
+ assert random_sample or (H == self.img_size[0] and W == self.img_size[1]), f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
66
+
67
+ x = self.proj(x)
68
+ if self.flatten:
69
+ x = rearrange(x, "B C H W -> B (H W) C")
70
+ else:
71
+ x = rearrange(x, "B C H W -> B H W C")
72
+ x = self.norm(x)
73
+ return x
74
+
75
+
76
+ class TimestepEmbedder(nn.Module):
77
+ """
78
+ Embeds scalar timesteps into vector representations.
79
+ """
80
+
81
+ def __init__(self, hidden_size, frequency_embedding_size=256, freq_type='time_step'):
82
+ super().__init__()
83
+ self.mlp = nn.Sequential(
84
+ nn.Linear(frequency_embedding_size, hidden_size, bias=True), # hidden_size is diffusion model hidden size
85
+ nn.SiLU(),
86
+ nn.Linear(hidden_size, hidden_size, bias=True),
87
+ )
88
+ self.frequency_embedding_size = frequency_embedding_size
89
+ self.freq_type = freq_type
90
+
91
+ @staticmethod
92
+ def timestep_embedding(t, dim, max_period=10000, freq_type='time_step'):
93
+ """
94
+ Create sinusoidal timestep embeddings.
95
+ :param t: a 1-D Tensor of N indices, one per batch element.
96
+ These may be fractional.
97
+ :param dim: the dimension of the output.
98
+ :param max_period: controls the minimum frequency of the embeddings.
99
+ :return: an (N, D) Tensor of positional embeddings.
100
+ """
101
+ # https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
102
+ half = dim // 2
103
+
104
+ if freq_type == 'time_step':
105
+ freqs = torch.exp(-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half).to(device=t.device)
106
+ elif freq_type == 'spatial': # ~(-5 5)
107
+ freqs = torch.linspace(1.0, half, half).to(device=t.device) * torch.pi
108
+ elif freq_type == 'angle': # 0-360
109
+ freqs = torch.linspace(1.0, half, half).to(device=t.device) * torch.pi / 180
110
+
111
+
112
+ args = t[:, None].float() * freqs[None]
113
+
114
+ embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
115
+ if dim % 2:
116
+ embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
117
+ return embedding
118
+
119
+ def forward(self, t):
120
+ t_freq = self.timestep_embedding(t, self.frequency_embedding_size, freq_type=self.freq_type)
121
+ t_emb = self.mlp(t_freq)
122
+ return t_emb
123
+
124
+
125
+ class FinalLayer(nn.Module):
126
+ """
127
+ The final layer of DiT.
128
+ """
129
+
130
+ def __init__(self, hidden_size, patch_size, out_channels):
131
+ super().__init__()
132
+ self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
133
+ self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
134
+ self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=True))
135
+
136
+ def forward(self, x, c):
137
+ shift, scale = self.adaLN_modulation(c).chunk(2, dim=-1)
138
+ x = modulate(self.norm_final(x), shift, scale)
139
+ x = self.linear(x)
140
+ return x
141
+
142
+
143
+ class SpatioTemporalDiTBlock(nn.Module):
144
+ def __init__(
145
+ self,
146
+ hidden_size,
147
+ num_heads,
148
+ reference_length,
149
+ mlp_ratio=4.0,
150
+ is_causal=True,
151
+ spatial_rotary_emb: Optional[RotaryEmbedding] = None,
152
+ temporal_rotary_emb: Optional[RotaryEmbedding] = None,
153
+ reference_rotary_emb=None,
154
+ use_plucker=False,
155
+ relative_embedding=False,
156
+ state_embed_only_on_qk=False,
157
+ use_memory_attention=False,
158
+ ref_mode='sequential'
159
+ ):
160
+ super().__init__()
161
+ self.is_causal = is_causal
162
+ mlp_hidden_dim = int(hidden_size * mlp_ratio)
163
+ approx_gelu = lambda: nn.GELU(approximate="tanh")
164
+
165
+ self.s_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
166
+ self.s_attn = SpatialAxialAttention(
167
+ hidden_size,
168
+ heads=num_heads,
169
+ dim_head=hidden_size // num_heads,
170
+ rotary_emb=spatial_rotary_emb
171
+ )
172
+ self.s_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
173
+ self.s_mlp = Mlp(
174
+ in_features=hidden_size,
175
+ hidden_features=mlp_hidden_dim,
176
+ act_layer=approx_gelu,
177
+ drop=0,
178
+ )
179
+ self.s_adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 6 * hidden_size, bias=True))
180
+
181
+ self.t_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
182
+ self.t_attn = TemporalAxialAttention(
183
+ hidden_size,
184
+ heads=num_heads,
185
+ dim_head=hidden_size // num_heads,
186
+ is_causal=is_causal,
187
+ rotary_emb=temporal_rotary_emb,
188
+ reference_length=reference_length
189
+ )
190
+ self.t_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
191
+ self.t_mlp = Mlp(
192
+ in_features=hidden_size,
193
+ hidden_features=mlp_hidden_dim,
194
+ act_layer=approx_gelu,
195
+ drop=0,
196
+ )
197
+ self.t_adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 6 * hidden_size, bias=True))
198
+
199
+ self.use_memory_attention = use_memory_attention
200
+ if self.use_memory_attention:
201
+ self.r_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
202
+ self.ref_type = "full_ref"
203
+ if self.ref_type == "temporal_ref":
204
+ self.r_attn = MemTemporalAxialAttention(
205
+ hidden_size,
206
+ heads=num_heads,
207
+ dim_head=hidden_size // num_heads,
208
+ is_causal=is_causal,
209
+ rotary_emb=None
210
+ )
211
+ elif self.ref_type == "full_ref":
212
+ self.r_attn = MemFullAttention(
213
+ hidden_size,
214
+ heads=num_heads,
215
+ dim_head=hidden_size // num_heads,
216
+ is_causal=is_causal,
217
+ rotary_emb=reference_rotary_emb,
218
+ reference_length=reference_length
219
+ )
220
+ self.r_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
221
+ self.r_mlp = Mlp(
222
+ in_features=hidden_size,
223
+ hidden_features=mlp_hidden_dim,
224
+ act_layer=approx_gelu,
225
+ drop=0,
226
+ )
227
+ self.r_adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 6 * hidden_size, bias=True))
228
+
229
+ self.use_plucker = use_plucker
230
+ if use_plucker:
231
+ self.pose_cond_mlp = nn.Linear(hidden_size, hidden_size)
232
+ self.temporal_pose_cond_mlp = nn.Linear(hidden_size, hidden_size)
233
+
234
+ self.reference_length = reference_length
235
+ self.relative_embedding = relative_embedding
236
+ self.state_embed_only_on_qk = state_embed_only_on_qk
237
+
238
+ self.ref_mode = ref_mode
239
+
240
+ if self.ref_mode == 'parallel':
241
+ self.parallel_map = nn.Linear(hidden_size, hidden_size)
242
+
243
+ def forward(self, x, c, current_frame=None, timestep=None, is_last_block=False,
244
+ pose_cond=None, mode="training", c_action_cond=None, reference_length=None):
245
+ B, T, H, W, D = x.shape
246
+
247
+ # spatial block
248
+
249
+ s_shift_msa, s_scale_msa, s_gate_msa, s_shift_mlp, s_scale_mlp, s_gate_mlp = self.s_adaLN_modulation(c).chunk(6, dim=-1)
250
+ x = x + gate(self.s_attn(modulate(self.s_norm1(x), s_shift_msa, s_scale_msa)), s_gate_msa)
251
+ x = x + gate(self.s_mlp(modulate(self.s_norm2(x), s_shift_mlp, s_scale_mlp)), s_gate_mlp)
252
+
253
+ # temporal block
254
+ if c_action_cond is not None:
255
+ t_shift_msa, t_scale_msa, t_gate_msa, t_shift_mlp, t_scale_mlp, t_gate_mlp = self.t_adaLN_modulation(c_action_cond).chunk(6, dim=-1)
256
+ else:
257
+ t_shift_msa, t_scale_msa, t_gate_msa, t_shift_mlp, t_scale_mlp, t_gate_mlp = self.t_adaLN_modulation(c).chunk(6, dim=-1)
258
+
259
+ x_t = x + gate(self.t_attn(modulate(self.t_norm1(x), t_shift_msa, t_scale_msa)), t_gate_msa)
260
+ x_t = x_t + gate(self.t_mlp(modulate(self.t_norm2(x_t), t_shift_mlp, t_scale_mlp)), t_gate_mlp)
261
+
262
+ if self.ref_mode == 'sequential':
263
+ x = x_t
264
+
265
+ # memory block
266
+ relative_embedding = self.relative_embedding # and mode == "training"
267
+
268
+ if self.use_memory_attention:
269
+ r_shift_msa, r_scale_msa, r_gate_msa, r_shift_mlp, r_scale_mlp, r_gate_mlp = self.r_adaLN_modulation(c).chunk(6, dim=-1)
270
+
271
+ if pose_cond is not None:
272
+ if self.use_plucker:
273
+ input_cond = self.pose_cond_mlp(pose_cond)
274
+
275
+ if relative_embedding:
276
+ n_frames = x.shape[1] - reference_length
277
+ x1_relative_embedding = []
278
+ r_shift_msa_relative_embedding = []
279
+ r_scale_msa_relative_embedding = []
280
+ for i in range(n_frames):
281
+ x1_relative_embedding.append(torch.cat([x[:,i:i+1], x[:, -reference_length:]], dim=1).clone())
282
+ r_shift_msa_relative_embedding.append(torch.cat([r_shift_msa[:,i:i+1], r_shift_msa[:, -reference_length:]], dim=1).clone())
283
+ r_scale_msa_relative_embedding.append(torch.cat([r_scale_msa[:,i:i+1], r_scale_msa[:, -reference_length:]], dim=1).clone())
284
+ x1_zero_frame = torch.cat(x1_relative_embedding, dim=1)
285
+ r_shift_msa = torch.cat(r_shift_msa_relative_embedding, dim=1)
286
+ r_scale_msa = torch.cat(r_scale_msa_relative_embedding, dim=1)
287
+
288
+ # if current_frame == 18:
289
+ # import pdb;pdb.set_trace()
290
+
291
+ if self.state_embed_only_on_qk:
292
+ attn_input = x1_zero_frame
293
+ extra_condition = input_cond
294
+ else:
295
+ attn_input = input_cond + x1_zero_frame
296
+ extra_condition = None
297
+ else:
298
+ attn_input = input_cond + x
299
+ extra_condition = None
300
+ # print("input_cond2:", input_cond.abs().mean())
301
+ # print("c:", c.abs().mean())
302
+ # input_cond = x1
303
+
304
+ x = x + gate(self.r_attn(modulate(self.r_norm1(attn_input), r_shift_msa, r_scale_msa),
305
+ relative_embedding=relative_embedding,
306
+ extra_condition=extra_condition,
307
+ state_embed_only_on_qk=self.state_embed_only_on_qk,
308
+ reference_length=reference_length), r_gate_msa)
309
+ else:
310
+ # pose_cond *= 0
311
+ x = x + gate(self.r_attn(modulate(self.r_norm1(x+pose_cond[:,:,None, None]), r_shift_msa, r_scale_msa),
312
+ current_frame=current_frame, timestep=timestep,
313
+ is_last_block=is_last_block,
314
+ reference_length=reference_length), r_gate_msa)
315
+ else:
316
+ x = x + gate(self.r_attn(modulate(self.r_norm1(x), r_shift_msa, r_scale_msa), current_frame=current_frame, timestep=timestep,
317
+ is_last_block=is_last_block), r_gate_msa)
318
+
319
+ x = x + gate(self.r_mlp(modulate(self.r_norm2(x), r_shift_mlp, r_scale_mlp)), r_gate_mlp)
320
+
321
+ if self.ref_mode == 'parallel':
322
+ x = x_t + self.parallel_map(x)
323
+
324
+ return x
325
+
326
+ # print((x1-x2).abs().sum())
327
+ # r_shift_msa, r_scale_msa, r_gate_msa, r_shift_mlp, r_scale_mlp, r_gate_mlp = self.r_adaLN_modulation(c).chunk(6, dim=-1)
328
+ # x2 = x1 + gate(self.r_attn(modulate(self.r_norm1(x_), r_shift_msa, r_scale_msa)), r_gate_msa)
329
+ # x2 = gate(self.r_mlp(modulate(self.r_norm2(x2), r_shift_mlp, r_scale_mlp)), r_gate_mlp)
330
+ # x = x1 + x2
331
+
332
+ # print(x.mean())
333
+ # return x
334
+
335
+
336
+ class DiT(nn.Module):
337
+ """
338
+ Diffusion model with a Transformer backbone.
339
+ """
340
+
341
+ def __init__(
342
+ self,
343
+ input_h=18,
344
+ input_w=32,
345
+ patch_size=2,
346
+ in_channels=16,
347
+ hidden_size=1024,
348
+ depth=12,
349
+ num_heads=16,
350
+ mlp_ratio=4.0,
351
+ action_cond_dim=25,
352
+ pose_cond_dim=4,
353
+ max_frames=32,
354
+ reference_length=8,
355
+ use_plucker=False,
356
+ relative_embedding=False,
357
+ state_embed_only_on_qk=False,
358
+ use_memory_attention=False,
359
+ add_timestamp_embedding=False,
360
+ ref_mode='sequential'
361
+ ):
362
+ super().__init__()
363
+ self.in_channels = in_channels
364
+ self.out_channels = in_channels
365
+ self.patch_size = patch_size
366
+ self.num_heads = num_heads
367
+ self.max_frames = max_frames
368
+
369
+ self.x_embedder = PatchEmbed(input_h, input_w, patch_size, in_channels, hidden_size, flatten=False)
370
+ self.t_embedder = TimestepEmbedder(hidden_size)
371
+
372
+ self.add_timestamp_embedding = add_timestamp_embedding
373
+ if self.add_timestamp_embedding:
374
+ self.timestamp_embedding = TimestepEmbedder(hidden_size)
375
+
376
+ frame_h, frame_w = self.x_embedder.grid_size
377
+
378
+ self.spatial_rotary_emb = RotaryEmbedding(dim=hidden_size // num_heads // 2, freqs_for="pixel", max_freq=256)
379
+ self.temporal_rotary_emb = RotaryEmbedding(dim=hidden_size // num_heads)
380
+ # self.reference_rotary_emb = RotaryEmbedding(dim=hidden_size // num_heads // 2, freqs_for="pixel", max_freq=256)
381
+ self.reference_rotary_emb = None
382
+
383
+ self.external_cond = nn.Linear(action_cond_dim, hidden_size) if action_cond_dim > 0 else nn.Identity()
384
+
385
+ # self.pose_cond = nn.Linear(pose_cond_dim, hidden_size) if pose_cond_dim > 0 else nn.Identity()
386
+
387
+ self.use_plucker = use_plucker
388
+ if not self.use_plucker:
389
+ self.position_embedder = TimestepEmbedder(hidden_size, freq_type='spatial')
390
+ self.angle_embedder = TimestepEmbedder(hidden_size, freq_type='angle')
391
+ else:
392
+ self.pose_embedder = SimpleCameraPoseEncoder(c_in=6, c_out=hidden_size)
393
+
394
+ self.blocks = nn.ModuleList(
395
+ [
396
+ SpatioTemporalDiTBlock(
397
+ hidden_size,
398
+ num_heads,
399
+ mlp_ratio=mlp_ratio,
400
+ is_causal=True,
401
+ reference_length=reference_length,
402
+ spatial_rotary_emb=self.spatial_rotary_emb,
403
+ temporal_rotary_emb=self.temporal_rotary_emb,
404
+ reference_rotary_emb=self.reference_rotary_emb,
405
+ use_plucker=self.use_plucker,
406
+ relative_embedding=relative_embedding,
407
+ state_embed_only_on_qk=state_embed_only_on_qk,
408
+ use_memory_attention=use_memory_attention,
409
+ ref_mode=ref_mode
410
+ )
411
+ for _ in range(depth)
412
+ ]
413
+ )
414
+ self.use_memory_attention = use_memory_attention
415
+ self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels)
416
+ self.initialize_weights()
417
+
418
+ def initialize_weights(self):
419
+ # Initialize transformer layers:
420
+ def _basic_init(module):
421
+ if isinstance(module, nn.Linear):
422
+ torch.nn.init.xavier_uniform_(module.weight)
423
+ if module.bias is not None:
424
+ nn.init.constant_(module.bias, 0)
425
+
426
+ self.apply(_basic_init)
427
+
428
+ # Initialize patch_embed like nn.Linear (instead of nn.Conv2d):
429
+ w = self.x_embedder.proj.weight.data
430
+ nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
431
+ nn.init.constant_(self.x_embedder.proj.bias, 0)
432
+
433
+ # Initialize timestep embedding MLP:
434
+ nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
435
+ nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
436
+
437
+ if self.use_memory_attention:
438
+ if not self.use_plucker:
439
+ nn.init.normal_(self.position_embedder.mlp[0].weight, std=0.02)
440
+ nn.init.normal_(self.position_embedder.mlp[2].weight, std=0.02)
441
+
442
+ nn.init.normal_(self.angle_embedder.mlp[0].weight, std=0.02)
443
+ nn.init.normal_(self.angle_embedder.mlp[2].weight, std=0.02)
444
+
445
+ if self.add_timestamp_embedding:
446
+ nn.init.normal_(self.timestamp_embedding.mlp[0].weight, std=0.02)
447
+ nn.init.normal_(self.timestamp_embedding.mlp[2].weight, std=0.02)
448
+
449
+
450
+ # Zero-out adaLN modulation layers in DiT blocks:
451
+ for block in self.blocks:
452
+ nn.init.constant_(block.s_adaLN_modulation[-1].weight, 0)
453
+ nn.init.constant_(block.s_adaLN_modulation[-1].bias, 0)
454
+ nn.init.constant_(block.t_adaLN_modulation[-1].weight, 0)
455
+ nn.init.constant_(block.t_adaLN_modulation[-1].bias, 0)
456
+
457
+ if self.use_plucker and self.use_memory_attention:
458
+ nn.init.constant_(block.pose_cond_mlp.weight, 0)
459
+ nn.init.constant_(block.pose_cond_mlp.bias, 0)
460
+
461
+ # Zero-out output layers:
462
+ nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
463
+ nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
464
+ nn.init.constant_(self.final_layer.linear.weight, 0)
465
+ nn.init.constant_(self.final_layer.linear.bias, 0)
466
+
467
+ def unpatchify(self, x):
468
+ """
469
+ x: (N, H, W, patch_size**2 * C)
470
+ imgs: (N, H, W, C)
471
+ """
472
+ c = self.out_channels
473
+ p = self.x_embedder.patch_size[0]
474
+ h = x.shape[1]
475
+ w = x.shape[2]
476
+
477
+ x = x.reshape(shape=(x.shape[0], h, w, p, p, c))
478
+ x = torch.einsum("nhwpqc->nchpwq", x)
479
+ imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p))
480
+ return imgs
481
+
482
+ def forward(self, x, t, action_cond=None, pose_cond=None, current_frame=None, mode=None,
483
+ reference_length=None, frame_idx=None):
484
+ """
485
+ Forward pass of DiT.
486
+ x: (B, T, C, H, W) tensor of spatial inputs (images or latent representations of images)
487
+ t: (B, T,) tensor of diffusion timesteps
488
+ """
489
+
490
+ B, T, C, H, W = x.shape
491
+
492
+ # add spatial embeddings
493
+ x = rearrange(x, "b t c h w -> (b t) c h w")
494
+
495
+ x = self.x_embedder(x) # (B*T, C, H, W) -> (B*T, H/2, W/2, D) , C = 16, D = d_model
496
+ # restore shape
497
+ x = rearrange(x, "(b t) h w d -> b t h w d", t=T)
498
+ # embed noise steps
499
+ t = rearrange(t, "b t -> (b t)")
500
+
501
+ c_t = self.t_embedder(t) # (N, D)
502
+ c = c_t.clone()
503
+ c = rearrange(c, "(b t) d -> b t d", t=T)
504
+
505
+ if torch.is_tensor(action_cond):
506
+ try:
507
+ c_action_cond = c + self.external_cond(action_cond)
508
+ except:
509
+ import pdb;pdb.set_trace()
510
+ else:
511
+ c_action_cond = None
512
+
513
+ if torch.is_tensor(pose_cond):
514
+ if not self.use_plucker:
515
+ pose_cond = pose_cond.to(action_cond.dtype)
516
+ b_, t_, d_ = pose_cond.shape
517
+ pos_emb = self.position_embedder(rearrange(pose_cond[...,:3], "b t d -> (b t d)"))
518
+ angle_emb = self.angle_embedder(rearrange(pose_cond[...,3:], "b t d -> (b t d)"))
519
+ pos_emb = rearrange(pos_emb, "(b t d) c -> b t d c", b=b_, t=t_, d=3).sum(-2)
520
+ angle_emb = rearrange(angle_emb, "(b t d) c -> b t d c", b=b_, t=t_, d=2).sum(-2)
521
+ pc = pos_emb + angle_emb
522
+ else:
523
+ pose_cond = pose_cond[:, :, ::40, ::40]
524
+ # pc = self.pose_embedder(pose_cond)[0]
525
+ # pc = pc.permute(0,2,3,4,1)
526
+ pc = self.pose_embedder(pose_cond)
527
+ pc = pc.permute(1,0,2,3,4)
528
+
529
+ if torch.is_tensor(frame_idx) and self.add_timestamp_embedding:
530
+ bb = frame_idx.shape[1]
531
+ frame_idx = rearrange(frame_idx, "t b -> (b t)")
532
+ frame_idx = self.timestamp_embedding(frame_idx)
533
+ frame_idx = rearrange(frame_idx, "(b t) d -> b t d", b=bb)
534
+ pc = pc + frame_idx[:, :, None, None]
535
+
536
+ # pc = pc + rearrange(c_t.clone(), "(b t) d -> b t d", t=T)[:,:,None,None] # add time condition for different timestep scaling
537
+ else:
538
+ pc = None
539
+
540
+ for i, block in enumerate(self.blocks):
541
+ x = block(x, c, current_frame=current_frame, timestep=t, is_last_block= (i+1 == len(self.blocks)),
542
+ pose_cond=pc, mode=mode, c_action_cond=c_action_cond, reference_length=reference_length) # (N, T, H, W, D)
543
+ x = self.final_layer(x, c) # (N, T, H, W, patch_size ** 2 * out_channels)
544
+ # unpatchify
545
+ x = rearrange(x, "b t h w d -> (b t) h w d")
546
+ x = self.unpatchify(x) # (N, out_channels, H, W)
547
+ x = rearrange(x, "(b t) c h w -> b t c h w", t=T)
548
+ return x
549
+
550
+
551
+ def DiT_S_2(action_cond_dim, pose_cond_dim, reference_length,
552
+ use_plucker, relative_embedding,
553
+ state_embed_only_on_qk, use_memory_attention, add_timestamp_embedding,
554
+ ref_mode):
555
+ return DiT(
556
+ patch_size=2,
557
+ hidden_size=1024,
558
+ depth=16,
559
+ num_heads=16,
560
+ action_cond_dim=action_cond_dim,
561
+ pose_cond_dim=pose_cond_dim,
562
+ reference_length=reference_length,
563
+ use_plucker=use_plucker,
564
+ relative_embedding=relative_embedding,
565
+ state_embed_only_on_qk=state_embed_only_on_qk,
566
+ use_memory_attention=use_memory_attention,
567
+ add_timestamp_embedding=add_timestamp_embedding,
568
+ ref_mode=ref_mode
569
+ )
570
+
571
+
572
+ DiT_models = {"DiT-S/2": DiT_S_2}
algorithms/dememwm/models/pose_prediction.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ class PosePredictionNet(nn.Module):
6
+ def __init__(self, img_channels=16, img_feat_dim=256, pose_dim=5, action_dim=25, hidden_dim=128):
7
+ super(PosePredictionNet, self).__init__()
8
+
9
+ self.cnn = nn.Sequential(
10
+ nn.Conv2d(img_channels, 32, kernel_size=3, stride=2, padding=1),
11
+ nn.ReLU(),
12
+ nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1),
13
+ nn.ReLU(),
14
+ nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1),
15
+ nn.ReLU(),
16
+ nn.AdaptiveAvgPool2d((1, 1))
17
+ )
18
+
19
+ self.fc_img = nn.Linear(128, img_feat_dim)
20
+
21
+ self.mlp_motion = nn.Sequential(
22
+ nn.Linear(pose_dim + action_dim, hidden_dim),
23
+ nn.ReLU(),
24
+ nn.Linear(hidden_dim, hidden_dim),
25
+ nn.ReLU()
26
+ )
27
+
28
+ self.fc_out = nn.Sequential(
29
+ nn.Linear(img_feat_dim + hidden_dim, hidden_dim),
30
+ nn.ReLU(),
31
+ nn.Linear(hidden_dim, pose_dim)
32
+ )
33
+
34
+ def forward(self, img, action, pose):
35
+ img_feat = self.cnn(img).view(img.size(0), -1)
36
+ img_feat = self.fc_img(img_feat)
37
+
38
+ motion_feat = self.mlp_motion(torch.cat([pose, action], dim=1))
39
+ fused_feat = torch.cat([img_feat, motion_feat], dim=1)
40
+ pose_next_pred = self.fc_out(fused_feat)
41
+
42
+ return pose_next_pred
algorithms/dememwm/models/rotary_embedding_torch.py ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adapted from https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py
3
+ """
4
+
5
+ from __future__ import annotations
6
+ from math import pi, log
7
+
8
+ import torch
9
+ from torch.nn import Module, ModuleList
10
+ from torch.amp import autocast
11
+ from torch import nn, einsum, broadcast_tensors, Tensor
12
+
13
+ from einops import rearrange, repeat
14
+
15
+ from typing import Literal
16
+
17
+ # helper functions
18
+
19
+
20
+ def exists(val):
21
+ return val is not None
22
+
23
+
24
+ def default(val, d):
25
+ return val if exists(val) else d
26
+
27
+
28
+ # broadcat, as tortoise-tts was using it
29
+
30
+
31
+ def broadcat(tensors, dim=-1):
32
+ broadcasted_tensors = broadcast_tensors(*tensors)
33
+ return torch.cat(broadcasted_tensors, dim=dim)
34
+
35
+
36
+ # rotary embedding helper functions
37
+
38
+
39
+ def rotate_half(x):
40
+ x = rearrange(x, "... (d r) -> ... d r", r=2)
41
+ x1, x2 = x.unbind(dim=-1)
42
+ x = torch.stack((-x2, x1), dim=-1)
43
+ return rearrange(x, "... d r -> ... (d r)")
44
+
45
+
46
+ @autocast("cuda", enabled=False)
47
+ def apply_rotary_emb(freqs, t, start_index=0, scale=1.0, seq_dim=-2):
48
+ dtype = t.dtype
49
+
50
+ if t.ndim == 3:
51
+ seq_len = t.shape[seq_dim]
52
+ freqs = freqs[-seq_len:]
53
+
54
+ rot_dim = freqs.shape[-1]
55
+ end_index = start_index + rot_dim
56
+
57
+ assert rot_dim <= t.shape[-1], f"feature dimension {t.shape[-1]} is not of sufficient size to rotate in all the positions {rot_dim}"
58
+
59
+ # Split t into three parts: left, middle (to be transformed), and right
60
+ t_left = t[..., :start_index]
61
+ t_middle = t[..., start_index:end_index]
62
+ t_right = t[..., end_index:]
63
+
64
+ # Apply rotary embeddings without modifying t in place
65
+ t_transformed = (t_middle * freqs.cos() * scale) + (rotate_half(t_middle) * freqs.sin() * scale)
66
+
67
+ out = torch.cat((t_left, t_transformed, t_right), dim=-1)
68
+
69
+ return out.type(dtype)
70
+
71
+
72
+ # learned rotation helpers
73
+
74
+
75
+ def apply_learned_rotations(rotations, t, start_index=0, freq_ranges=None):
76
+ if exists(freq_ranges):
77
+ rotations = einsum("..., f -> ... f", rotations, freq_ranges)
78
+ rotations = rearrange(rotations, "... r f -> ... (r f)")
79
+
80
+ rotations = repeat(rotations, "... n -> ... (n r)", r=2)
81
+ return apply_rotary_emb(rotations, t, start_index=start_index)
82
+
83
+
84
+ # classes
85
+
86
+
87
+ class RotaryEmbedding(Module):
88
+ def __init__(
89
+ self,
90
+ dim,
91
+ custom_freqs: Tensor | None = None,
92
+ freqs_for: Literal["lang", "pixel", "constant"] = "lang",
93
+ theta=10000,
94
+ max_freq=10,
95
+ num_freqs=1,
96
+ learned_freq=False,
97
+ use_xpos=False,
98
+ xpos_scale_base=512,
99
+ interpolate_factor=1.0,
100
+ theta_rescale_factor=1.0,
101
+ seq_before_head_dim=False,
102
+ cache_if_possible=True,
103
+ cache_max_seq_len=8192,
104
+ ):
105
+ super().__init__()
106
+ # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
107
+ # has some connection to NTK literature
108
+ # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
109
+
110
+ theta *= theta_rescale_factor ** (dim / (dim - 2))
111
+
112
+ self.freqs_for = freqs_for
113
+
114
+ if exists(custom_freqs):
115
+ freqs = custom_freqs
116
+ elif freqs_for == "lang":
117
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
118
+ elif freqs_for == "pixel":
119
+ freqs = torch.linspace(1.0, max_freq / 2, dim // 2) * pi
120
+ elif freqs_for == "spacetime":
121
+ time_freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
122
+ freqs = torch.linspace(1.0, max_freq / 2, dim // 2) * pi
123
+ elif freqs_for == "constant":
124
+ freqs = torch.ones(num_freqs).float()
125
+
126
+ if freqs_for == "spacetime":
127
+ self.time_freqs = nn.Parameter(time_freqs, requires_grad=learned_freq)
128
+ self.freqs = nn.Parameter(freqs, requires_grad=learned_freq)
129
+
130
+ self.cache_if_possible = cache_if_possible
131
+ self.cache_max_seq_len = cache_max_seq_len
132
+
133
+ self.register_buffer("cached_freqs", torch.zeros(cache_max_seq_len, dim), persistent=False)
134
+ self.register_buffer("cached_freqs_seq_len", torch.tensor(0), persistent=False)
135
+
136
+ self.learned_freq = learned_freq
137
+
138
+ # dummy for device
139
+
140
+ self.register_buffer("dummy", torch.tensor(0), persistent=False)
141
+
142
+ # default sequence dimension
143
+
144
+ self.seq_before_head_dim = seq_before_head_dim
145
+ self.default_seq_dim = -3 if seq_before_head_dim else -2
146
+
147
+ # interpolation factors
148
+
149
+ assert interpolate_factor >= 1.0
150
+ self.interpolate_factor = interpolate_factor
151
+
152
+ # xpos
153
+
154
+ self.use_xpos = use_xpos
155
+
156
+ if not use_xpos:
157
+ return
158
+
159
+ scale = (torch.arange(0, dim, 2) + 0.4 * dim) / (1.4 * dim)
160
+ self.scale_base = xpos_scale_base
161
+
162
+ self.register_buffer("scale", scale, persistent=False)
163
+ self.register_buffer("cached_scales", torch.zeros(cache_max_seq_len, dim), persistent=False)
164
+ self.register_buffer("cached_scales_seq_len", torch.tensor(0), persistent=False)
165
+
166
+ # add apply_rotary_emb as static method
167
+
168
+ self.apply_rotary_emb = staticmethod(apply_rotary_emb)
169
+
170
+ @property
171
+ def device(self):
172
+ return self.dummy.device
173
+
174
+ def get_seq_pos(self, seq_len, device, dtype, offset=0):
175
+ return (torch.arange(seq_len, device=device, dtype=dtype) + offset) / self.interpolate_factor
176
+
177
+ def rotate_queries_or_keys(self, t, freqs, seq_dim=None, offset=0, scale=None):
178
+ seq_dim = default(seq_dim, self.default_seq_dim)
179
+
180
+ assert not self.use_xpos or exists(scale), "you must use `.rotate_queries_and_keys` method instead and pass in both queries and keys, for length extrapolatable rotary embeddings"
181
+
182
+ device, dtype, seq_len = t.device, t.dtype, t.shape[seq_dim]
183
+
184
+ seq = self.get_seq_pos(seq_len, device=device, dtype=dtype, offset=offset)
185
+
186
+ seq_freqs = self.forward(seq, freqs, seq_len=seq_len, offset=offset)
187
+
188
+ if seq_dim == -3:
189
+ seq_freqs = rearrange(seq_freqs, "n d -> n 1 d")
190
+
191
+ return apply_rotary_emb(seq_freqs, t, scale=default(scale, 1.0), seq_dim=seq_dim)
192
+
193
+ def rotate_queries_with_cached_keys(self, q, k, seq_dim=None, offset=0):
194
+ dtype, device, seq_dim = (
195
+ q.dtype,
196
+ q.device,
197
+ default(seq_dim, self.default_seq_dim),
198
+ )
199
+
200
+ q_len, k_len = q.shape[seq_dim], k.shape[seq_dim]
201
+ assert q_len <= k_len
202
+
203
+ q_scale = k_scale = 1.0
204
+
205
+ if self.use_xpos:
206
+ seq = self.get_seq_pos(k_len, dtype=dtype, device=device)
207
+
208
+ q_scale = self.get_scale(seq[-q_len:]).type(dtype)
209
+ k_scale = self.get_scale(seq).type(dtype)
210
+
211
+ rotated_q = self.rotate_queries_or_keys(q, seq_dim=seq_dim, scale=q_scale, offset=k_len - q_len + offset)
212
+ rotated_k = self.rotate_queries_or_keys(k, seq_dim=seq_dim, scale=k_scale**-1)
213
+
214
+ rotated_q = rotated_q.type(q.dtype)
215
+ rotated_k = rotated_k.type(k.dtype)
216
+
217
+ return rotated_q, rotated_k
218
+
219
+ def rotate_queries_and_keys(self, q, k, freqs, seq_dim=None):
220
+ seq_dim = default(seq_dim, self.default_seq_dim)
221
+
222
+ assert self.use_xpos
223
+ device, dtype, seq_len = q.device, q.dtype, q.shape[seq_dim]
224
+
225
+ seq = self.get_seq_pos(seq_len, dtype=dtype, device=device)
226
+
227
+ seq_freqs = self.forward(seq, freqs, seq_len=seq_len)
228
+ scale = self.get_scale(seq, seq_len=seq_len).to(dtype)
229
+
230
+ if seq_dim == -3:
231
+ seq_freqs = rearrange(seq_freqs, "n d -> n 1 d")
232
+ scale = rearrange(scale, "n d -> n 1 d")
233
+
234
+ rotated_q = apply_rotary_emb(seq_freqs, q, scale=scale, seq_dim=seq_dim)
235
+ rotated_k = apply_rotary_emb(seq_freqs, k, scale=scale**-1, seq_dim=seq_dim)
236
+
237
+ rotated_q = rotated_q.type(q.dtype)
238
+ rotated_k = rotated_k.type(k.dtype)
239
+
240
+ return rotated_q, rotated_k
241
+
242
+ def get_scale(self, t: Tensor, seq_len: int | None = None, offset=0):
243
+ assert self.use_xpos
244
+
245
+ should_cache = self.cache_if_possible and exists(seq_len) and (offset + seq_len) <= self.cache_max_seq_len
246
+
247
+ if should_cache and exists(self.cached_scales) and (seq_len + offset) <= self.cached_scales_seq_len.item():
248
+ return self.cached_scales[offset : (offset + seq_len)]
249
+
250
+ scale = 1.0
251
+ if self.use_xpos:
252
+ power = (t - len(t) // 2) / self.scale_base
253
+ scale = self.scale ** rearrange(power, "n -> n 1")
254
+ scale = repeat(scale, "n d -> n (d r)", r=2)
255
+
256
+ if should_cache and offset == 0:
257
+ self.cached_scales[:seq_len] = scale.detach()
258
+ self.cached_scales_seq_len.copy_(seq_len)
259
+
260
+ return scale
261
+
262
+ def get_axial_freqs(self, *dims):
263
+ Colon = slice(None)
264
+ all_freqs = []
265
+
266
+ for ind, dim in enumerate(dims):
267
+ # only allow pixel freqs for last two dimensions
268
+ use_pixel = (self.freqs_for == "pixel" or self.freqs_for == "spacetime") and ind >= len(dims) - 2
269
+ if use_pixel:
270
+ pos = torch.linspace(-1, 1, steps=dim, device=self.device)
271
+ else:
272
+ pos = torch.arange(dim, device=self.device)
273
+
274
+ if self.freqs_for == "spacetime" and not use_pixel:
275
+ seq_freqs = self.forward(pos, self.time_freqs, seq_len=dim)
276
+ else:
277
+ seq_freqs = self.forward(pos, self.freqs, seq_len=dim)
278
+
279
+ all_axis = [None] * len(dims)
280
+ all_axis[ind] = Colon
281
+
282
+ new_axis_slice = (Ellipsis, *all_axis, Colon)
283
+ all_freqs.append(seq_freqs[new_axis_slice])
284
+
285
+ all_freqs = broadcast_tensors(*all_freqs)
286
+ return torch.cat(all_freqs, dim=-1)
287
+
288
+ @autocast("cuda", enabled=False)
289
+ def forward(self, t: Tensor, freqs: Tensor, seq_len=None, offset=0):
290
+ should_cache = self.cache_if_possible and not self.learned_freq and exists(seq_len) and self.freqs_for != "pixel" and (offset + seq_len) <= self.cache_max_seq_len
291
+
292
+ if should_cache and exists(self.cached_freqs) and (offset + seq_len) <= self.cached_freqs_seq_len.item():
293
+ return self.cached_freqs[offset : (offset + seq_len)].detach()
294
+
295
+ freqs = einsum("..., f -> ... f", t.type(freqs.dtype), freqs)
296
+ freqs = repeat(freqs, "... n -> ... (n r)", r=2)
297
+
298
+ if should_cache and offset == 0:
299
+ self.cached_freqs[:seq_len] = freqs.detach()
300
+ self.cached_freqs_seq_len.copy_(seq_len)
301
+
302
+ return freqs
algorithms/dememwm/models/utils.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Adapted from https://github.com/buoyancy99/diffusion-forcing/blob/main/algorithms/diffusion_forcing/models/utils.py
3
+ Action format derived from VPT https://github.com/openai/Video-Pre-Training
4
+ Adapted from https://github.com/etched-ai/open-oasis/blob/master/utils.py
5
+ """
6
+
7
+ import math
8
+ import torch
9
+ from torch import nn
10
+ from torchvision.io import read_image, read_video
11
+ from torchvision.transforms.functional import resize
12
+ from einops import rearrange
13
+ from typing import Mapping, Sequence
14
+ from einops import rearrange, parse_shape
15
+
16
+
17
+ def exists(val):
18
+ return val is not None
19
+
20
+
21
+ def default(val, d):
22
+ if exists(val):
23
+ return val
24
+ return d() if callable(d) else d
25
+
26
+
27
+ def extract(a, t, x_shape):
28
+ f, b = t.shape
29
+ out = a[t]
30
+ return out.reshape(f, b, *((1,) * (len(x_shape) - 2)))
31
+
32
+
33
+ def linear_beta_schedule(timesteps):
34
+ """
35
+ linear schedule, proposed in original ddpm paper
36
+ """
37
+ scale = 1000 / timesteps
38
+ beta_start = scale * 0.0001
39
+ beta_end = scale * 0.02
40
+ return torch.linspace(beta_start, beta_end, timesteps, dtype=torch.float64)
41
+
42
+
43
+ def cosine_beta_schedule(timesteps, s=0.008):
44
+ """
45
+ cosine schedule
46
+ as proposed in https://openreview.net/forum?id=-NEXDKk8gZ
47
+ """
48
+ steps = timesteps + 1
49
+ t = torch.linspace(0, timesteps, steps, dtype=torch.float64) / timesteps
50
+ alphas_cumprod = torch.cos((t + s) / (1 + s) * math.pi * 0.5) ** 2
51
+ alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
52
+ betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
53
+ return torch.clip(betas, 0, 0.999)
54
+
55
+
56
+
57
+ def sigmoid_beta_schedule(timesteps, start=-3, end=3, tau=1, clamp_min=1e-5):
58
+ """
59
+ sigmoid schedule
60
+ proposed in https://arxiv.org/abs/2212.11972 - Figure 8
61
+ better for images > 64x64, when used during training
62
+ """
63
+ steps = timesteps + 1
64
+ t = torch.linspace(0, timesteps, steps, dtype=torch.float64) / timesteps
65
+ v_start = torch.tensor(start / tau).sigmoid()
66
+ v_end = torch.tensor(end / tau).sigmoid()
67
+ alphas_cumprod = (-((t * (end - start) + start) / tau).sigmoid() + v_end) / (v_end - v_start)
68
+ alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
69
+ betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
70
+ return torch.clip(betas, 0, 0.999)
71
+
72
+
73
+ ACTION_KEYS = [
74
+ "inventory",
75
+ "ESC",
76
+ "hotbar.1",
77
+ "hotbar.2",
78
+ "hotbar.3",
79
+ "hotbar.4",
80
+ "hotbar.5",
81
+ "hotbar.6",
82
+ "hotbar.7",
83
+ "hotbar.8",
84
+ "hotbar.9",
85
+ "forward",
86
+ "back",
87
+ "left",
88
+ "right",
89
+ "cameraX",
90
+ "cameraY",
91
+ "jump",
92
+ "sneak",
93
+ "sprint",
94
+ "swapHands",
95
+ "attack",
96
+ "use",
97
+ "pickItem",
98
+ "drop",
99
+ ]
100
+
101
+
102
+ def one_hot_actions(actions: Sequence[Mapping[str, int]]) -> torch.Tensor:
103
+ actions_one_hot = torch.zeros(len(actions), len(ACTION_KEYS))
104
+ for i, current_actions in enumerate(actions):
105
+ for j, action_key in enumerate(ACTION_KEYS):
106
+ if action_key.startswith("camera"):
107
+ if action_key == "cameraX":
108
+ value = current_actions["camera"][0]
109
+ elif action_key == "cameraY":
110
+ value = current_actions["camera"][1]
111
+ else:
112
+ raise ValueError(f"Unknown camera action key: {action_key}")
113
+ max_val = 20
114
+ bin_size = 0.5
115
+ num_buckets = int(max_val / bin_size)
116
+ value = (value - num_buckets) / num_buckets
117
+ assert -1 - 1e-3 <= value <= 1 + 1e-3, f"Camera action value must be in [-1, 1], got {value}"
118
+ else:
119
+ value = current_actions[action_key]
120
+ assert 0 <= value <= 1, f"Action value must be in [0, 1] got {value}"
121
+ actions_one_hot[i, j] = value
122
+
123
+ return actions_one_hot
124
+
125
+
126
+ IMAGE_EXTENSIONS = {"png", "jpg", "jpeg"}
127
+ VIDEO_EXTENSIONS = {"mp4"}
128
+
129
+
130
+ def load_prompt(path, video_offset=None, n_prompt_frames=1):
131
+ if path.lower().split(".")[-1] in IMAGE_EXTENSIONS:
132
+ print("prompt is image; ignoring video_offset and n_prompt_frames")
133
+ prompt = read_image(path)
134
+ # add frame dimension
135
+ prompt = rearrange(prompt, "c h w -> 1 c h w")
136
+ elif path.lower().split(".")[-1] in VIDEO_EXTENSIONS:
137
+ prompt = read_video(path, pts_unit="sec")[0]
138
+ if video_offset is not None:
139
+ prompt = prompt[video_offset:]
140
+ prompt = prompt[:n_prompt_frames]
141
+ else:
142
+ raise ValueError(f"unrecognized prompt file extension; expected one in {IMAGE_EXTENSIONS} or {VIDEO_EXTENSIONS}")
143
+ assert prompt.shape[0] == n_prompt_frames, f"input prompt {path} had less than n_prompt_frames={n_prompt_frames} frames"
144
+ prompt = resize(prompt, (360, 640))
145
+ # add batch dimension
146
+ prompt = rearrange(prompt, "t c h w -> 1 t c h w")
147
+ prompt = prompt.float() / 255.0
148
+ return prompt
149
+
150
+
151
+ def load_actions(path, action_offset=None):
152
+ if path.endswith(".actions.pt"):
153
+ actions = one_hot_actions(torch.load(path))
154
+ elif path.endswith(".one_hot_actions.pt"):
155
+ actions = torch.load(path, weights_only=True)
156
+ else:
157
+ raise ValueError("unrecognized action file extension; expected '*.actions.pt' or '*.one_hot_actions.pt'")
158
+ if action_offset is not None:
159
+ actions = actions[action_offset:]
160
+ actions = torch.cat([torch.zeros_like(actions[:1]), actions], dim=0)
161
+ # add batch dimension
162
+ actions = rearrange(actions, "t d -> 1 t d")
163
+ return actions
algorithms/dememwm/models/vae.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ References:
3
+ - VQGAN: https://github.com/CompVis/taming-transformers
4
+ - MAE: https://github.com/facebookresearch/mae
5
+ """
6
+
7
+ import numpy as np
8
+ import math
9
+ import functools
10
+ from collections import namedtuple
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+ from einops import rearrange
15
+ from timm.models.vision_transformer import Mlp
16
+ from timm.layers.helpers import to_2tuple
17
+ from rotary_embedding_torch import RotaryEmbedding, apply_rotary_emb
18
+ from .dit import PatchEmbed
19
+
20
+
21
+ class DiagonalGaussianDistribution(object):
22
+ def __init__(self, parameters, deterministic=False, dim=1):
23
+ self.parameters = parameters
24
+ self.mean, self.logvar = torch.chunk(parameters, 2, dim=dim)
25
+ if dim == 1:
26
+ self.dims = [1, 2, 3]
27
+ elif dim == 2:
28
+ self.dims = [1, 2]
29
+ else:
30
+ raise NotImplementedError
31
+ self.logvar = torch.clamp(self.logvar, -30.0, 20.0)
32
+ self.deterministic = deterministic
33
+ self.std = torch.exp(0.5 * self.logvar)
34
+ self.var = torch.exp(self.logvar)
35
+ if self.deterministic:
36
+ self.var = self.std = torch.zeros_like(self.mean).to(device=self.parameters.device)
37
+
38
+ def sample(self):
39
+ x = self.mean + self.std * torch.randn(self.mean.shape).to(device=self.parameters.device)
40
+ return x
41
+
42
+ def mode(self):
43
+ return self.mean
44
+
45
+
46
+ class Attention(nn.Module):
47
+ def __init__(
48
+ self,
49
+ dim,
50
+ num_heads,
51
+ frame_height,
52
+ frame_width,
53
+ qkv_bias=False,
54
+ ):
55
+ super().__init__()
56
+ self.num_heads = num_heads
57
+ head_dim = dim // num_heads
58
+ self.frame_height = frame_height
59
+ self.frame_width = frame_width
60
+
61
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
62
+ self.proj = nn.Linear(dim, dim)
63
+
64
+ rotary_freqs = RotaryEmbedding(
65
+ dim=head_dim // 4,
66
+ freqs_for="pixel",
67
+ max_freq=frame_height * frame_width,
68
+ ).get_axial_freqs(frame_height, frame_width)
69
+ self.register_buffer("rotary_freqs", rotary_freqs, persistent=False)
70
+
71
+ def forward(self, x):
72
+ B, N, C = x.shape
73
+ assert N == self.frame_height * self.frame_width
74
+
75
+ q, k, v = self.qkv(x).chunk(3, dim=-1)
76
+
77
+ q = rearrange(
78
+ q,
79
+ "b (H W) (h d) -> b h H W d",
80
+ H=self.frame_height,
81
+ W=self.frame_width,
82
+ h=self.num_heads,
83
+ )
84
+ k = rearrange(
85
+ k,
86
+ "b (H W) (h d) -> b h H W d",
87
+ H=self.frame_height,
88
+ W=self.frame_width,
89
+ h=self.num_heads,
90
+ )
91
+ v = rearrange(
92
+ v,
93
+ "b (H W) (h d) -> b h H W d",
94
+ H=self.frame_height,
95
+ W=self.frame_width,
96
+ h=self.num_heads,
97
+ )
98
+
99
+ q = apply_rotary_emb(self.rotary_freqs, q)
100
+ k = apply_rotary_emb(self.rotary_freqs, k)
101
+
102
+ q = rearrange(q, "b h H W d -> b h (H W) d")
103
+ k = rearrange(k, "b h H W d -> b h (H W) d")
104
+ v = rearrange(v, "b h H W d -> b h (H W) d")
105
+
106
+ x = F.scaled_dot_product_attention(q, k, v)
107
+ x = rearrange(x, "b h N d -> b N (h d)")
108
+
109
+ x = self.proj(x)
110
+ return x
111
+
112
+
113
+ class AttentionBlock(nn.Module):
114
+ def __init__(
115
+ self,
116
+ dim,
117
+ num_heads,
118
+ frame_height,
119
+ frame_width,
120
+ mlp_ratio=4.0,
121
+ qkv_bias=False,
122
+ attn_causal=False,
123
+ act_layer=nn.GELU,
124
+ norm_layer=nn.LayerNorm,
125
+ ):
126
+ super().__init__()
127
+ self.norm1 = norm_layer(dim)
128
+ self.attn = Attention(
129
+ dim,
130
+ num_heads,
131
+ frame_height,
132
+ frame_width,
133
+ qkv_bias=qkv_bias,
134
+ )
135
+ self.norm2 = norm_layer(dim)
136
+ mlp_hidden_dim = int(dim * mlp_ratio)
137
+ self.mlp = Mlp(
138
+ in_features=dim,
139
+ hidden_features=mlp_hidden_dim,
140
+ act_layer=act_layer,
141
+ )
142
+
143
+ def forward(self, x):
144
+ x = x + self.attn(self.norm1(x))
145
+ x = x + self.mlp(self.norm2(x))
146
+ return x
147
+
148
+
149
+ class AutoencoderKL(nn.Module):
150
+ def __init__(
151
+ self,
152
+ latent_dim,
153
+ input_height=256,
154
+ input_width=256,
155
+ patch_size=16,
156
+ enc_dim=768,
157
+ enc_depth=6,
158
+ enc_heads=12,
159
+ dec_dim=768,
160
+ dec_depth=6,
161
+ dec_heads=12,
162
+ mlp_ratio=4.0,
163
+ norm_layer=functools.partial(nn.LayerNorm, eps=1e-6),
164
+ use_variational=True,
165
+ **kwargs,
166
+ ):
167
+ super().__init__()
168
+ self.input_height = input_height
169
+ self.input_width = input_width
170
+ self.patch_size = patch_size
171
+ self.seq_h = input_height // patch_size
172
+ self.seq_w = input_width // patch_size
173
+ self.seq_len = self.seq_h * self.seq_w
174
+ self.patch_dim = 3 * patch_size**2
175
+
176
+ self.latent_dim = latent_dim
177
+ self.enc_dim = enc_dim
178
+ self.dec_dim = dec_dim
179
+
180
+ # patch
181
+ self.patch_embed = PatchEmbed(input_height, input_width, patch_size, 3, enc_dim)
182
+
183
+ # encoder
184
+ self.encoder = nn.ModuleList(
185
+ [
186
+ AttentionBlock(
187
+ enc_dim,
188
+ enc_heads,
189
+ self.seq_h,
190
+ self.seq_w,
191
+ mlp_ratio,
192
+ qkv_bias=True,
193
+ norm_layer=norm_layer,
194
+ )
195
+ for i in range(enc_depth)
196
+ ]
197
+ )
198
+ self.enc_norm = norm_layer(enc_dim)
199
+
200
+ # bottleneck
201
+ self.use_variational = use_variational
202
+ mult = 2 if self.use_variational else 1
203
+ self.quant_conv = nn.Linear(enc_dim, mult * latent_dim)
204
+ self.post_quant_conv = nn.Linear(latent_dim, dec_dim)
205
+
206
+ # decoder
207
+ self.decoder = nn.ModuleList(
208
+ [
209
+ AttentionBlock(
210
+ dec_dim,
211
+ dec_heads,
212
+ self.seq_h,
213
+ self.seq_w,
214
+ mlp_ratio,
215
+ qkv_bias=True,
216
+ norm_layer=norm_layer,
217
+ )
218
+ for i in range(dec_depth)
219
+ ]
220
+ )
221
+ self.dec_norm = norm_layer(dec_dim)
222
+ self.predictor = nn.Linear(dec_dim, self.patch_dim) # decoder to patch
223
+
224
+ # initialize this weight first
225
+ self.initialize_weights()
226
+
227
+ def initialize_weights(self):
228
+ # initialization
229
+ # initialize nn.Linear and nn.LayerNorm
230
+ self.apply(self._init_weights)
231
+
232
+ # initialize patch_embed like nn.Linear (instead of nn.Conv2d)
233
+ w = self.patch_embed.proj.weight.data
234
+ nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
235
+
236
+ def _init_weights(self, m):
237
+ if isinstance(m, nn.Linear):
238
+ # we use xavier_uniform following official JAX ViT:
239
+ nn.init.xavier_uniform_(m.weight)
240
+ if isinstance(m, nn.Linear) and m.bias is not None:
241
+ nn.init.constant_(m.bias, 0.0)
242
+ elif isinstance(m, nn.LayerNorm):
243
+ nn.init.constant_(m.bias, 0.0)
244
+ nn.init.constant_(m.weight, 1.0)
245
+
246
+ def patchify(self, x):
247
+ # patchify
248
+ bsz, _, h, w = x.shape
249
+ x = x.reshape(
250
+ bsz,
251
+ 3,
252
+ self.seq_h,
253
+ self.patch_size,
254
+ self.seq_w,
255
+ self.patch_size,
256
+ ).permute([0, 1, 3, 5, 2, 4]) # [b, c, h, p, w, p] --> [b, c, p, p, h, w]
257
+ x = x.reshape(bsz, self.patch_dim, self.seq_h, self.seq_w) # --> [b, cxpxp, h, w]
258
+ x = x.permute([0, 2, 3, 1]).reshape(bsz, self.seq_len, self.patch_dim) # --> [b, hxw, cxpxp]
259
+ return x
260
+
261
+ def unpatchify(self, x):
262
+ bsz = x.shape[0]
263
+ # unpatchify
264
+ x = x.reshape(bsz, self.seq_h, self.seq_w, self.patch_dim).permute([0, 3, 1, 2]) # [b, h, w, cxpxp] --> [b, cxpxp, h, w]
265
+ x = x.reshape(
266
+ bsz,
267
+ 3,
268
+ self.patch_size,
269
+ self.patch_size,
270
+ self.seq_h,
271
+ self.seq_w,
272
+ ).permute([0, 1, 4, 2, 5, 3]) # [b, c, p, p, h, w] --> [b, c, h, p, w, p]
273
+ x = x.reshape(
274
+ bsz,
275
+ 3,
276
+ self.input_height,
277
+ self.input_width,
278
+ ) # [b, c, hxp, wxp]
279
+ return x
280
+
281
+ def encode(self, x):
282
+ # patchify
283
+ x = self.patch_embed(x)
284
+
285
+ # encoder
286
+ for blk in self.encoder:
287
+ x = blk(x)
288
+ x = self.enc_norm(x)
289
+
290
+ # bottleneck
291
+ moments = self.quant_conv(x)
292
+ if not self.use_variational:
293
+ moments = torch.cat((moments, torch.zeros_like(moments)), 2)
294
+ posterior = DiagonalGaussianDistribution(moments, deterministic=(not self.use_variational), dim=2)
295
+ return posterior
296
+
297
+ def decode(self, z):
298
+ # bottleneck
299
+ z = self.post_quant_conv(z)
300
+
301
+ # decoder
302
+ for blk in self.decoder:
303
+ z = blk(z)
304
+ z = self.dec_norm(z)
305
+
306
+ # predictor
307
+ z = self.predictor(z)
308
+
309
+ # unpatchify
310
+ dec = self.unpatchify(z)
311
+ return dec
312
+
313
+ def autoencode(self, input, sample_posterior=True):
314
+ posterior = self.encode(input)
315
+ if self.use_variational and sample_posterior:
316
+ z = posterior.sample()
317
+ else:
318
+ z = posterior.mode()
319
+ dec = self.decode(z)
320
+ return dec, posterior, z
321
+
322
+ def get_input(self, batch, k):
323
+ x = batch[k]
324
+ if len(x.shape) == 3:
325
+ x = x[..., None]
326
+ x = x.permute(0, 3, 1, 2).to(memory_format=torch.contiguous_format).float()
327
+ return x
328
+
329
+ def forward(self, inputs, labels, split="train"):
330
+ rec, post, latent = self.autoencode(inputs)
331
+ return rec, post, latent
332
+
333
+ def get_last_layer(self):
334
+ return self.predictor.weight
335
+
336
+
337
+ def ViT_L_20_Shallow_Encoder(**kwargs):
338
+ if "latent_dim" in kwargs:
339
+ latent_dim = kwargs.pop("latent_dim")
340
+ else:
341
+ latent_dim = 16
342
+ return AutoencoderKL(
343
+ latent_dim=latent_dim,
344
+ patch_size=20,
345
+ enc_dim=1024,
346
+ enc_depth=6,
347
+ enc_heads=16,
348
+ dec_dim=1024,
349
+ dec_depth=12,
350
+ dec_heads=16,
351
+ input_height=360,
352
+ input_width=640,
353
+ **kwargs,
354
+ )
355
+
356
+
357
+ VAE_models = {
358
+ "vit-l-20-shallow-encoder": ViT_L_20_Shallow_Encoder,
359
+ }
algorithms/dememwm/pose_prediction.py ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from omegaconf import DictConfig
2
+ import torch
3
+ from lightning.pytorch.utilities.types import STEP_OUTPUT
4
+ from algorithms.common.metrics import (
5
+ FrechetInceptionDistance,
6
+ LearnedPerceptualImagePatchSimilarity,
7
+ FrechetVideoDistance,
8
+ )
9
+ from .df_base import DiffusionForcingBase
10
+ from utils.logging_utils import log_video, get_validation_metrics_for_videos
11
+ from .models.vae import VAE_models
12
+ from .models.dit import DiT_models
13
+ from einops import rearrange
14
+ from torch import autocast
15
+ import numpy as np
16
+ from tqdm import tqdm
17
+ import torch.nn.functional as F
18
+ from .models.pose_prediction import PosePredictionNet
19
+ import torchvision.transforms.functional as TF
20
+ import random
21
+ from torchvision.transforms import InterpolationMode
22
+ from PIL import Image
23
+ import math
24
+ from packaging import version as pver
25
+ import torch.distributed as dist
26
+ import matplotlib.pyplot as plt
27
+
28
+ import torch
29
+ import math
30
+ import wandb
31
+
32
+ import torch.nn as nn
33
+ from algorithms.common.base_pytorch_algo import BasePytorchAlgo
34
+
35
+ class PosePrediction(BasePytorchAlgo):
36
+
37
+ def __init__(self, cfg: DictConfig):
38
+
39
+ super().__init__(cfg)
40
+
41
+ def _build_model(self):
42
+ self.pose_prediction_model = PosePredictionNet()
43
+ vae = VAE_models["vit-l-20-shallow-encoder"]()
44
+ self.vae = vae.eval()
45
+
46
+ def training_step(self, batch, batch_idx) -> STEP_OUTPUT:
47
+ xs, conditions, pose_conditions= batch
48
+ pose_conditions[:,:,3:] = pose_conditions[:,:,3:] // 15
49
+ xs = self.encode(xs)
50
+
51
+ b,f,c,h,w = xs.shape
52
+ xs = xs[:,:-1].reshape(-1, c, h, w)
53
+ conditions = conditions[:,1:].reshape(-1, 25)
54
+ offset_gt = pose_conditions[:,1:] - pose_conditions[:,:-1]
55
+ pose_conditions = pose_conditions[:,:-1].reshape(-1, 5)
56
+ offset_gt = offset_gt.reshape(-1, 5)
57
+ offset_gt[:, 3][offset_gt[:, 3]==23] = -1
58
+ offset_gt[:, 3][offset_gt[:, 3]==-23] = 1
59
+ offset_gt[:, 4][offset_gt[:, 4]==23] = -1
60
+ offset_gt[:, 4][offset_gt[:, 4]==-23] = 1
61
+
62
+ offset_pred = self.pose_prediction_model(xs, conditions, pose_conditions)
63
+ criterion = nn.MSELoss()
64
+ loss = criterion(offset_pred, offset_gt)
65
+ if batch_idx % 200 == 0:
66
+ self.log("training/loss", loss.cpu())
67
+ output_dict = {
68
+ "loss": loss}
69
+ return output_dict
70
+
71
+ def encode(self, x):
72
+ # vae encoding
73
+ B = x.shape[1]
74
+ T = x.shape[0]
75
+ H, W = x.shape[-2:]
76
+ scaling_factor = 0.07843137255
77
+
78
+ x = rearrange(x, "t b c h w -> (t b) c h w")
79
+ with torch.no_grad():
80
+ with autocast("cuda", dtype=torch.half):
81
+ x = self.vae.encode(x * 2 - 1).mean * scaling_factor
82
+ x = rearrange(x, "(t b) (h w) c -> t b c h w", t=T, h=H // self.vae.patch_size, w=W // self.vae.patch_size)
83
+ # x = x[:, :n_prompt_frames]
84
+ return x
85
+
86
+ def decode(self, x):
87
+ total_frames = x.shape[0]
88
+ scaling_factor = 0.07843137255
89
+ x = rearrange(x, "t b c h w -> (t b) (h w) c")
90
+ with torch.no_grad():
91
+ with autocast("cuda", dtype=torch.half):
92
+ x = (self.vae.decode(x / scaling_factor) + 1) / 2
93
+
94
+ x = rearrange(x, "(t b) c h w-> t b c h w", t=total_frames)
95
+ return x
96
+
97
+ def validation_step(self, batch, batch_idx, namespace="validation") -> STEP_OUTPUT:
98
+ xs, conditions, pose_conditions= batch
99
+ pose_conditions[:,:,3:] = pose_conditions[:,:,3:] // 15
100
+ xs = self.encode(xs)
101
+
102
+ b,f,c,h,w = xs.shape
103
+ xs = xs[:,:-1].reshape(-1, c, h, w)
104
+ conditions = conditions[:,1:].reshape(-1, 25)
105
+ offset_gt = pose_conditions[:,1:] - pose_conditions[:,:-1]
106
+ pose_conditions = pose_conditions[:,:-1].reshape(-1, 5)
107
+ offset_gt = offset_gt.reshape(-1, 5)
108
+ offset_gt[:, 3][offset_gt[:, 3]==23] = -1
109
+ offset_gt[:, 3][offset_gt[:, 3]==-23] = 1
110
+ offset_gt[:, 4][offset_gt[:, 4]==23] = -1
111
+ offset_gt[:, 4][offset_gt[:, 4]==-23] = 1
112
+
113
+ offset_pred = self.pose_prediction_model(xs, conditions, pose_conditions)
114
+
115
+ criterion = nn.MSELoss()
116
+ loss = criterion(offset_pred, offset_gt)
117
+
118
+ if batch_idx % 200 == 0:
119
+ self.log("validation/loss", loss.cpu())
120
+ output_dict = {
121
+ "loss": loss}
122
+ return
123
+
124
+ @torch.no_grad()
125
+ def interactive(self, batch, context_frames, device):
126
+ with torch.cuda.amp.autocast():
127
+ condition_similar_length = self.condition_similar_length
128
+ # xs_raw, conditions, pose_conditions, c2w_mat, masks, frame_idx = self._preprocess_batch(batch)
129
+
130
+ first_frame, new_conditions, new_pose_conditions, new_c2w_mat, new_frame_idx = batch
131
+
132
+ if self.frames is None:
133
+ first_frame_encode = self.encode(first_frame[None, None].to(device))
134
+ self.frames = first_frame_encode.to(device)
135
+ self.actions = new_conditions[None, None].to(device)
136
+ self.poses = new_pose_conditions[None, None].to(device)
137
+ self.memory_c2w = new_c2w_mat[None, None].to(device)
138
+ self.frame_idx = torch.tensor([[new_frame_idx]]).to(device)
139
+ return first_frame
140
+ else:
141
+ self.actions = torch.cat([self.actions, new_conditions[None, None].to(device)])
142
+ self.poses = torch.cat([self.poses, new_pose_conditions[None, None].to(device)])
143
+ self.memory_c2w = torch.cat([self.memory_c2w, new_c2w_mat[None, None].to(device)])
144
+ self.frame_idx = torch.cat([self.frame_idx, torch.tensor([[new_frame_idx]]).to(device)])
145
+
146
+ conditions = self.actions.clone()
147
+ pose_conditions = self.poses.clone()
148
+ c2w_mat = self.memory_c2w .clone()
149
+ frame_idx = self.frame_idx.clone()
150
+
151
+
152
+ curr_frame = 0
153
+ horizon = 1
154
+ batch_size = 1
155
+ n_frames = curr_frame + horizon
156
+ # context
157
+ n_context_frames = context_frames // self.frame_stack
158
+ xs_pred = self.frames[:n_context_frames].clone()
159
+ curr_frame += n_context_frames
160
+
161
+ pbar = tqdm(total=n_frames, initial=curr_frame, desc="Sampling")
162
+
163
+ # generation on frame
164
+ scheduling_matrix = self._generate_scheduling_matrix(horizon)
165
+ chunk = torch.randn((horizon, batch_size, *xs_pred.shape[2:])).to(xs_pred.device)
166
+ chunk = torch.clamp(chunk, -self.clip_noise, self.clip_noise)
167
+
168
+ xs_pred = torch.cat([xs_pred, chunk], 0)
169
+
170
+ # sliding window: only input the last n_tokens frames
171
+ start_frame = max(0, curr_frame + horizon - self.n_tokens)
172
+
173
+ pbar.set_postfix(
174
+ {
175
+ "start": start_frame,
176
+ "end": curr_frame + horizon,
177
+ }
178
+ )
179
+
180
+ if condition_similar_length:
181
+
182
+ if curr_frame < condition_similar_length:
183
+ random_idx = [i for i in range(curr_frame)] + [0] * (condition_similar_length-curr_frame)
184
+ random_idx = np.repeat(np.array(random_idx)[:,None], xs_pred.shape[1], -1)
185
+ else:
186
+ num_samples = 10000
187
+ radius = 30
188
+ samples = torch.rand((num_samples, 1), device=pose_conditions.device)
189
+ angles = 2 * np.pi * torch.rand((num_samples,), device=pose_conditions.device)
190
+ # points = radius * torch.sqrt(samples) * torch.stack((torch.cos(angles), torch.sin(angles)), dim=1)
191
+
192
+ points = generate_points_in_sphere(num_samples, radius).to(pose_conditions.device)
193
+ points = points[:, None].repeat(1, pose_conditions.shape[1], 1)
194
+ points += pose_conditions[curr_frame, :, :3][None]
195
+ fov_half_h = torch.tensor(105/2, device=pose_conditions.device)
196
+ fov_half_v = torch.tensor(75/2, device=pose_conditions.device)
197
+ # in_fov1 = is_inside_fov(points, pose_conditions[curr_frame, :, [0, 2]], pose_conditions[curr_frame, :, -1], fov_half)
198
+
199
+ in_fov1 = is_inside_fov_3d_hv(points, pose_conditions[curr_frame, :, :3],
200
+ pose_conditions[curr_frame, :, -2], pose_conditions[curr_frame, :, -1],
201
+ fov_half_h, fov_half_v)
202
+
203
+ in_fov_list = []
204
+ for pc in pose_conditions[:curr_frame]:
205
+ in_fov_list.append(is_inside_fov_3d_hv(points, pc[:, :3], pc[:, -2], pc[:, -1],
206
+ fov_half_h, fov_half_v))
207
+
208
+ in_fov_list = torch.stack(in_fov_list)
209
+ # v3
210
+ random_idx = []
211
+
212
+ for csl in range(self.condition_similar_length // 2):
213
+ overlap_ratio = ((in_fov1[None].bool() & in_fov_list).sum(1))/in_fov1.sum()
214
+ # mask = distance > (in_fov1.bool().sum(0) / 4)
215
+ #_, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
216
+
217
+ # if csl > self.condition_similar_length:
218
+ # _, r_idx = torch.topk(overlap_ratio, k=1, dim=0)
219
+ # else:
220
+ # _, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
221
+
222
+ _, r_idx = torch.topk(overlap_ratio, k=1, dim=0)
223
+ # _, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
224
+
225
+ # if curr_frame >=93:
226
+ # import pdb;pdb.set_trace()
227
+
228
+ # start_time = time.time()
229
+ cos_sim = F.cosine_similarity(xs_pred.to(r_idx.device)[r_idx[:, range(in_fov1.shape[1])],
230
+ range(in_fov1.shape[1])], xs_pred.to(r_idx.device)[:curr_frame], dim=2)
231
+ cos_sim = cos_sim.mean((-2,-1))
232
+
233
+ mask_sim = cos_sim>0.9
234
+ in_fov_list = in_fov_list & ~mask_sim[:,None].to(in_fov_list.device)
235
+
236
+ random_idx.append(r_idx)
237
+
238
+ for bi in range(conditions.shape[1]):
239
+ if len(torch.nonzero(conditions[:,bi,24] == 1))==0:
240
+ pass
241
+ else:
242
+ last_idx = torch.nonzero(conditions[:,bi,24] == 1)[-1]
243
+ in_fov_list[:last_idx,:,bi] = False
244
+
245
+ for csl in range(self.condition_similar_length // 2):
246
+ overlap_ratio = ((in_fov1[None].bool() & in_fov_list).sum(1))/in_fov1.sum()
247
+ # mask = distance > (in_fov1.bool().sum(0) / 4)
248
+ #_, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
249
+
250
+ # if csl > self.condition_similar_length:
251
+ # _, r_idx = torch.topk(overlap_ratio, k=1, dim=0)
252
+ # else:
253
+ # _, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
254
+
255
+ _, r_idx = torch.topk(overlap_ratio, k=1, dim=0)
256
+ # _, r_idx = torch.topk(overlap_ratio / tensor_max_with_number((frame_idx[curr_frame] - frame_idx[:curr_frame]), 10), k=1, dim=0)
257
+
258
+ # if curr_frame >=93:
259
+ # import pdb;pdb.set_trace()
260
+
261
+ # start_time = time.time()
262
+ cos_sim = F.cosine_similarity(xs_pred.to(r_idx.device)[r_idx[:, range(in_fov1.shape[1])],
263
+ range(in_fov1.shape[1])], xs_pred.to(r_idx.device)[:curr_frame], dim=2)
264
+ cos_sim = cos_sim.mean((-2,-1))
265
+
266
+ mask_sim = cos_sim>0.9
267
+ in_fov_list = in_fov_list & ~mask_sim[:,None].to(in_fov_list.device)
268
+
269
+ random_idx.append(r_idx)
270
+
271
+ random_idx = torch.cat(random_idx).cpu()
272
+ condition_similar_length = len(random_idx)
273
+
274
+ xs_pred = torch.cat([xs_pred, xs_pred[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])].clone()], 0)
275
+
276
+ if condition_similar_length:
277
+ # import pdb;pdb.set_trace()
278
+ padding = torch.zeros((condition_similar_length,) + conditions.shape[1:], device=conditions.device, dtype=conditions.dtype)
279
+ input_condition = torch.cat([conditions[start_frame : curr_frame + horizon], padding], dim=0)
280
+ if self.pose_cond_dim:
281
+ # if not self.use_plucker:
282
+ input_pose_condition = torch.cat([pose_conditions[start_frame : curr_frame + horizon], pose_conditions[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]], dim=0).clone()
283
+
284
+ if self.use_plucker:
285
+ if self.all_zero_frame:
286
+ frame_idx_list = []
287
+ input_pose_condition = []
288
+ for i in range(start_frame, curr_frame + horizon):
289
+ input_pose_condition.append(convert_to_plucker(torch.cat([c2w_mat[i:i+1],c2w_mat[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]]).clone(), 0, focal_length=self.focal_length, is_old_setting=self.old_setting).to(xs_pred.dtype))
290
+ frame_idx_list.append(torch.cat([frame_idx[i:i+1]-frame_idx[i:i+1], frame_idx[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]-frame_idx[i:i+1]]))
291
+ input_pose_condition = torch.cat(input_pose_condition)
292
+ frame_idx_list = torch.cat(frame_idx_list)
293
+
294
+ # print(frame_idx_list[:,0])
295
+ else:
296
+ # print(curr_frame-start_frame)
297
+ # input_pose_condition = torch.cat([c2w_mat[start_frame : curr_frame + horizon], c2w_mat[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]], dim=0).clone()
298
+ # import pdb;pdb.set_trace()
299
+ if self.last_frame_refer:
300
+ input_pose_condition = torch.cat([c2w_mat[start_frame : curr_frame + horizon], c2w_mat[-1:]], dim=0).clone()
301
+ else:
302
+ input_pose_condition = torch.cat([c2w_mat[start_frame : curr_frame + horizon], c2w_mat[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]], dim=0).clone()
303
+
304
+ if self.zero_curr:
305
+ # print("="*50)
306
+ input_pose_condition = convert_to_plucker(input_pose_condition, curr_frame-start_frame, focal_length=self.focal_length, is_old_setting=self.old_setting)
307
+ # input_pose_condition[:curr_frame-start_frame] = input_pose_condition[curr_frame-start_frame:curr_frame-start_frame+1]
308
+ # input_pose_condition = convert_to_plucker(input_pose_condition, -self.condition_similar_length-1, focal_length=self.focal_length)
309
+ else:
310
+ input_pose_condition = convert_to_plucker(input_pose_condition, -condition_similar_length, focal_length=self.focal_length, is_old_setting=self.old_setting)
311
+ frame_idx_list = None
312
+ else:
313
+ input_pose_condition = torch.cat([pose_conditions[start_frame : curr_frame + horizon], pose_conditions[random_idx[:,range(xs_pred.shape[1])], range(xs_pred.shape[1])]], dim=0).clone()
314
+ frame_idx_list = None
315
+ else:
316
+ input_condition = conditions[start_frame : curr_frame + horizon]
317
+ input_pose_condition = None
318
+ frame_idx_list = None
319
+
320
+ for m in range(scheduling_matrix.shape[0] - 1):
321
+ from_noise_levels = np.concatenate((np.zeros((curr_frame,), dtype=np.int64), scheduling_matrix[m]))[
322
+ :, None
323
+ ].repeat(batch_size, axis=1)
324
+ to_noise_levels = np.concatenate(
325
+ (
326
+ np.zeros((curr_frame,), dtype=np.int64),
327
+ scheduling_matrix[m + 1],
328
+ )
329
+ )[
330
+ :, None
331
+ ].repeat(batch_size, axis=1)
332
+
333
+ if condition_similar_length:
334
+ from_noise_levels = np.concatenate([from_noise_levels, np.zeros((condition_similar_length,from_noise_levels.shape[-1]), dtype=np.int32)], axis=0)
335
+ to_noise_levels = np.concatenate([to_noise_levels, np.zeros((condition_similar_length,from_noise_levels.shape[-1]), dtype=np.int32)], axis=0)
336
+
337
+ from_noise_levels = torch.from_numpy(from_noise_levels).to(self.device)
338
+ to_noise_levels = torch.from_numpy(to_noise_levels).to(self.device)
339
+
340
+
341
+ if input_pose_condition is not None:
342
+ input_pose_condition = input_pose_condition.to(xs_pred.dtype)
343
+
344
+ xs_pred[start_frame:] = self.diffusion_model.sample_step(
345
+ xs_pred[start_frame:],
346
+ input_condition,
347
+ input_pose_condition,
348
+ from_noise_levels[start_frame:],
349
+ to_noise_levels[start_frame:],
350
+ current_frame=curr_frame,
351
+ mode="validation",
352
+ reference_length=condition_similar_length,
353
+ frame_idx=frame_idx_list
354
+ )
355
+
356
+ # if curr_frame > 14:
357
+ # import pdb;pdb.set_trace()
358
+
359
+ # if xs_pred_back is not None:
360
+ # xs_pred = torch.cat([xs_pred[:6], xs_pred_back[6:12], xs_pred[6:]], dim=0)
361
+
362
+ # import pdb;pdb.set_trace()
363
+ if condition_similar_length: # and curr_frame+1!=n_frames:
364
+ xs_pred = xs_pred[:-condition_similar_length]
365
+
366
+ curr_frame += horizon
367
+ pbar.update(horizon)
368
+
369
+ self.frames = torch.cat([self.frames, xs_pred[n_context_frames:]])
370
+
371
+ xs_pred = self.decode(xs_pred[n_context_frames:])
372
+
373
+ return xs_pred[-1,0].cpu()
374
+
configurations/algorithm/dememwm_base.yaml ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - df_base
3
+
4
+ n_frames: ${dataset.n_frames}
5
+ frame_skip: ${dataset.frame_skip}
6
+ metadata: ${dataset.metadata}
7
+ memory_selection: ${dataset.memory_selection}
8
+
9
+ n_tokens: ${dataset.n_frames}
10
+ action_cond_dim: ${dataset.action_cond_dim}
11
+ pose_cond_dim: 5
12
+ use_plucker: true
13
+ relative_embedding: true
14
+ state_embed_only_on_qk: true
15
+ use_memory_attention: true
16
+ add_timestamp_embedding: true
17
+ ref_mode: sequential
18
+ focal_length: 0.35
19
+ log_video: false
20
+ save_local: true
21
+ require_pose_prediction: false
22
+
23
+ # training hyperparameters
24
+ weight_decay: 2e-3
25
+ warmup_steps: 1000
26
+ optimizer_beta: [0.9, 0.99]
27
+
28
+ diffusion:
29
+ beta_schedule: sigmoid
30
+ objective: pred_v
31
+ use_fused_snr: True
32
+ cum_snr_decay: 0.96
33
+ clip_noise: 20.
34
+ sampling_timesteps: 20
35
+ ddim_sampling_eta: 0.0
36
+ stabilization_level: 15
37
+ architecture:
38
+ network_size: 64
39
+ attn_heads: 4
40
+ attn_dim_head: 64
41
+ dim_mults: [1, 2, 4, 8]
42
+ resolution: ${dataset.resolution}
43
+ attn_resolutions: [16, 32, 64, 128]
44
+ use_init_temporal_attn: True
45
+ use_linear_attn: True
46
+ time_emb_type: rotary
47
+
48
+ _name: dememwm_base
configurations/dataset/video_minecraft_dememwm_latent.yaml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defaults:
2
+ - base_dataset
3
+
4
+ save_dir: data/minecraft_simple_backforward_latents
5
+ metadata: null
6
+ n_frames: 8
7
+ n_frames_valid: 500
8
+ frame_skip: 1
9
+ resolution: 128
10
+ image_hw: [360, 640]
11
+ observation_shape: [16, 18, 32]
12
+ data_mean: 0.0
13
+ data_std: 1.0
14
+ action_cond_dim: 25
15
+ context_length: 100
16
+ single_eval_clip: true
17
+ shuffle_clips: true
18
+
19
+ memory_selection:
20
+ enabled: true
21
+ max_anchor_frames: 2
22
+ max_dynamic_frames: 4
23
+ max_revisit_frames: 2
24
+ pose_similarity_threshold: 0.6
25
+ training_use_plucker: false
26
+ training_plucker_weight: 0.1
27
+ fov_overlap_threshold: 0.6
28
+ min_total_selected_coverage: 0.1
29
+ local_context_exclusion_frames: 8
30
+ plucker_moment_radius: 30.0
31
+ anchor_diverse_selection: true
32
+
33
+ _name: video_minecraft_dememwm_latent
datasets/video/__init__.py CHANGED
@@ -1 +1,2 @@
1
- from .minecraft_video_dataset import MinecraftVideoDataset
 
 
1
+ from .minecraft_video_dataset import MinecraftVideoDataset
2
+ from .minecraft_video_dememwm_latent_dataset import MinecraftVideoDeMemWMLatentDataset
datasets/video/memory_selection.py ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dataset-side memory selection for DeMemWM latent samples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict, Mapping, MutableMapping, Tuple
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+
11
+ SEGMENT_KEYS = ("anchor", "dynamic", "revisit")
12
+
13
+
14
+ def cfg_get(cfg, key: str, default=None):
15
+ if cfg is None:
16
+ return default
17
+ if isinstance(cfg, Mapping):
18
+ return cfg.get(key, default)
19
+ return getattr(cfg, key, default)
20
+
21
+
22
+ def _as_pose_array(poses) -> np.ndarray:
23
+ poses = np.asarray(poses, dtype=np.float32)
24
+ if poses.ndim != 2 or poses.shape[1] < 5:
25
+ raise ValueError("poses must have shape [num_frames, >=5]")
26
+ return poses[:, :5]
27
+
28
+
29
+ def _as_pose_tensor(poses) -> torch.Tensor:
30
+ pose_tensor = poses if torch.is_tensor(poses) else torch.as_tensor(poses, dtype=torch.float32)
31
+ pose_tensor = pose_tensor.to(dtype=torch.float32)
32
+ if pose_tensor.ndim != 2 or pose_tensor.shape[1] < 5:
33
+ raise ValueError("poses must have shape [num_frames, >=5]")
34
+ return pose_tensor[:, :5]
35
+
36
+
37
+ def _wrap_degrees(diff: torch.Tensor) -> torch.Tensor:
38
+ return torch.abs(torch.remainder(diff + 180.0, 360.0) - 180.0)
39
+
40
+
41
+ def _pose_directions(poses: torch.Tensor) -> torch.Tensor:
42
+ poses = _as_pose_tensor(poses)
43
+ pitch = torch.deg2rad(poses[:, 3])
44
+ yaw = torch.deg2rad(poses[:, 4])
45
+ cos_pitch = torch.cos(pitch)
46
+ directions = torch.stack(
47
+ [
48
+ torch.sin(yaw) * cos_pitch,
49
+ torch.sin(pitch),
50
+ torch.cos(yaw) * cos_pitch,
51
+ ],
52
+ dim=-1,
53
+ )
54
+ return directions / torch.linalg.vector_norm(directions, dim=-1, keepdim=True).clamp_min(1e-6)
55
+
56
+
57
+ def _sample_points_in_sphere(center: torch.Tensor, cfg=None) -> torch.Tensor:
58
+ num_points = max(1, int(cfg_get(cfg, "fov_num_points", 10000)))
59
+ radius = float(cfg_get(cfg, "fov_radius", cfg_get(cfg, "fov_spatial_radius", 30.0)))
60
+ seed = int(cfg_get(cfg, "fov_sample_seed", 0))
61
+ device = center.device
62
+ dtype = center.dtype
63
+ generator = torch.Generator(device=device)
64
+ generator.manual_seed(seed)
65
+
66
+ samples_r = torch.rand(num_points, device=device, dtype=dtype, generator=generator)
67
+ samples_phi = torch.rand(num_points, device=device, dtype=dtype, generator=generator)
68
+ samples_u = torch.rand(num_points, device=device, dtype=dtype, generator=generator)
69
+ r = radius * torch.pow(samples_r, 1.0 / 3.0)
70
+ phi = 2.0 * torch.pi * samples_phi
71
+ theta = torch.acos(1.0 - 2.0 * samples_u)
72
+
73
+ offsets = torch.stack(
74
+ [
75
+ r * torch.sin(theta) * torch.cos(phi),
76
+ r * torch.sin(theta) * torch.sin(phi),
77
+ r * torch.cos(theta),
78
+ ],
79
+ dim=-1,
80
+ )
81
+ return center[None, :3] + offsets
82
+
83
+
84
+ def _inside_fov_points(points: torch.Tensor, poses: torch.Tensor, cfg=None) -> torch.Tensor:
85
+ points = points.to(dtype=torch.float32)
86
+ poses = _as_pose_tensor(poses).to(device=points.device, dtype=torch.float32)
87
+ if points.numel() == 0 or poses.numel() == 0:
88
+ return torch.zeros((poses.shape[0], points.shape[0]), device=points.device, dtype=torch.bool)
89
+
90
+ fov_half_h = float(cfg_get(cfg, "fov_half_h", 105.0 / 2.0))
91
+ fov_half_v = float(cfg_get(cfg, "fov_half_v", 75.0 / 2.0))
92
+ vectors = points[None, :, :] - poses[:, None, :3]
93
+ x = vectors[..., 0]
94
+ y = vectors[..., 1]
95
+ z = vectors[..., 2]
96
+ azimuth = torch.rad2deg(torch.atan2(x, z))
97
+ elevation = torch.rad2deg(torch.atan2(y, torch.sqrt(x * x + z * z)))
98
+ yaw_diff = _wrap_degrees(azimuth - poses[:, None, 4])
99
+ pitch_diff = _wrap_degrees(elevation - poses[:, None, 3])
100
+ return (yaw_diff < fov_half_h) & (pitch_diff < fov_half_v)
101
+
102
+
103
+ def _target_fov_points(target_poses: torch.Tensor, cfg=None) -> torch.Tensor:
104
+ """Sample a WorldMem-style sphere and keep points visible in the target FOV."""
105
+
106
+ target_poses = _as_pose_tensor(target_poses)
107
+ if target_poses.shape[0] == 0:
108
+ return torch.zeros((0, 3), device=target_poses.device, dtype=target_poses.dtype)
109
+
110
+ points = _sample_points_in_sphere(target_poses[0], cfg)
111
+ target_inside = _inside_fov_points(points, target_poses, cfg).any(dim=0)
112
+ return points[target_inside]
113
+
114
+
115
+ def _pose_preselect(candidates: np.ndarray, poses_t: torch.Tensor, target_positions: np.ndarray, cfg=None) -> np.ndarray:
116
+ topk = cfg_get(cfg, "pose_preselect_topk", 64)
117
+ if topk is None:
118
+ return candidates
119
+ topk = int(topk)
120
+ if topk <= 0 or len(candidates) <= topk:
121
+ return candidates
122
+
123
+ candidates_t = torch.as_tensor(candidates, device=poses_t.device, dtype=torch.long)
124
+ targets_t = torch.as_tensor(target_positions, device=poses_t.device, dtype=torch.long)
125
+ radius = float(cfg_get(cfg, "fov_radius", cfg_get(cfg, "fov_spatial_radius", 30.0)))
126
+ candidate_poses = poses_t.index_select(0, candidates_t)
127
+ target_poses = poses_t.index_select(0, targets_t)
128
+ candidate_forward = _pose_directions(candidate_poses)
129
+ target_forward = _pose_directions(target_poses)
130
+ translation_norm = torch.linalg.vector_norm(candidate_poses[:, None, :3] - target_poses[None, :, :3], dim=-1) / max(radius, 1e-6)
131
+ dot = (candidate_forward[:, None, :] * target_forward[None, :, :]).sum(dim=-1).clamp(-1.0, 1.0)
132
+ angular = torch.acos(dot) / torch.pi
133
+ pose_distance = (translation_norm + angular).min(dim=1).values
134
+ rank = pose_distance.to(dtype=torch.float64) - candidates_t.to(dtype=torch.float64) * 1e-12
135
+ selected = torch.topk(rank, k=min(topk, int(candidates_t.numel())), largest=False, sorted=True).indices
136
+ return candidates_t.index_select(0, selected).cpu().numpy()
137
+
138
+
139
+ def _candidate_fov_masks(poses_t: torch.Tensor, candidates: np.ndarray, target_positions: np.ndarray, cfg=None) -> Tuple[torch.Tensor, torch.Tensor]:
140
+ targets_t = torch.as_tensor(target_positions, device=poses_t.device, dtype=torch.long)
141
+ points = _target_fov_points(poses_t.index_select(0, targets_t), cfg)
142
+ if len(candidates) == 0 or points.shape[0] == 0:
143
+ return (
144
+ torch.zeros((len(candidates), 0), device=poses_t.device, dtype=torch.bool),
145
+ torch.zeros((len(candidates),), device=poses_t.device, dtype=torch.float32),
146
+ )
147
+
148
+ candidates_t = torch.as_tensor(candidates, device=poses_t.device, dtype=torch.long)
149
+ chunk_size = int(cfg_get(cfg, "candidate_chunk_size", 64))
150
+ chunk_size = len(candidates) if chunk_size <= 0 else chunk_size
151
+ inside_parts = []
152
+ score_parts = []
153
+ for start in range(0, len(candidates), chunk_size):
154
+ chunk = candidates_t[start : start + chunk_size]
155
+ inside = _inside_fov_points(points, poses_t.index_select(0, chunk), cfg)
156
+ inside_parts.append(inside)
157
+ score_parts.append(inside.float().mean(dim=1))
158
+ return torch.cat(inside_parts, dim=0), torch.cat(score_parts, dim=0)
159
+
160
+
161
+ def _plucker_scores_tensor(candidate_poses: torch.Tensor, target_poses: torch.Tensor, cfg=None) -> torch.Tensor:
162
+ candidate_poses = _as_pose_tensor(candidate_poses)
163
+ target_poses = _as_pose_tensor(target_poses).to(device=candidate_poses.device)
164
+ if candidate_poses.shape[0] == 0 or target_poses.shape[0] == 0:
165
+ return torch.zeros((candidate_poses.shape[0],), device=candidate_poses.device, dtype=torch.float32)
166
+
167
+ moment_radius = float(cfg_get(cfg, "plucker_moment_radius", 30.0))
168
+ cand_dir = _pose_directions(candidate_poses)
169
+ target_dir = _pose_directions(target_poses)
170
+ cand_moment = torch.linalg.cross(candidate_poses[:, :3], cand_dir, dim=-1)
171
+ target_moment = torch.linalg.cross(target_poses[:, :3], target_dir, dim=-1)
172
+
173
+ direction_sim = (cand_dir @ target_dir.T).clamp(0.0, 1.0)
174
+ moment_dist = torch.linalg.vector_norm(cand_moment[:, None, :] - target_moment[None, :, :], dim=-1)
175
+ moment_sim = torch.exp(-moment_dist / max(moment_radius, 1e-6))
176
+ return (direction_sim * moment_sim).max(dim=1).values.to(dtype=torch.float32)
177
+
178
+
179
+ def _empty_selection(counts: Mapping[str, int]) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
180
+ indices = {}
181
+ masks = {}
182
+ for key in SEGMENT_KEYS:
183
+ count = int(counts.get(key, 0))
184
+ indices[key] = np.full((count,), -1, dtype=np.int64)
185
+ masks[key] = np.zeros((count,), dtype=bool)
186
+ return indices, masks
187
+
188
+
189
+ def _write_segment(indices: MutableMapping[str, np.ndarray], masks: MutableMapping[str, np.ndarray], key: str, selected) -> None:
190
+ selected = np.asarray(selected, dtype=np.int64)
191
+ count = len(indices[key])
192
+ if count == 0 or len(selected) == 0:
193
+ return
194
+ selected = selected[:count]
195
+ indices[key][: len(selected)] = selected
196
+ masks[key][: len(selected)] = True
197
+
198
+
199
+ def _best_row(rows: torch.Tensor, gains: torch.Tensor, fov_values: torch.Tensor, plucker: torch.Tensor, gaps: torch.Tensor, candidates_t: torch.Tensor) -> int:
200
+ active = rows
201
+ for values in (gains, fov_values, plucker, -gaps.to(dtype=torch.float32), -candidates_t.to(dtype=torch.float32)):
202
+ vals = values.index_select(0, active)
203
+ active = active.index_select(0, torch.nonzero(vals == vals.max(), as_tuple=False).flatten())
204
+ if active.numel() == 1:
205
+ break
206
+ return int(active[0].item())
207
+
208
+
209
+ def _select_by_pose_similarity(
210
+ poses: np.ndarray,
211
+ candidates: np.ndarray,
212
+ target_positions: np.ndarray,
213
+ cfg,
214
+ count: int,
215
+ rng=None,
216
+ ) -> np.ndarray:
217
+ if count <= 0 or len(candidates) == 0 or len(target_positions) == 0:
218
+ return np.empty((0,), dtype=np.int64)
219
+
220
+ poses_t = _as_pose_tensor(poses)
221
+ candidates_t = torch.as_tensor(candidates.astype(np.int64), device=poses_t.device, dtype=torch.long)
222
+ targets_t = torch.as_tensor(target_positions.astype(np.int64), device=poses_t.device, dtype=torch.long)
223
+ candidate_poses = poses_t.index_select(0, candidates_t)
224
+ target_poses = poses_t.index_select(0, targets_t)
225
+
226
+ # Training follows WorldMem's cheap pose-similarity sampling, not the 10k-point FOV selector.
227
+ spatial_scale = max(float(cfg_get(cfg, "pose_similarity_radius", cfg_get(cfg, "fov_radius", 30.0))), 1e-6)
228
+ angle_scale = max(float(cfg_get(cfg, "pose_similarity_angle_scale", 180.0)), 1e-6)
229
+ spatial_delta = torch.linalg.vector_norm(candidate_poses[:, None, :3] - target_poses[None, :, :3], dim=-1)
230
+ angle_delta = _wrap_degrees(candidate_poses[:, None, 3:] - target_poses[None, :, 3:]).mean(dim=-1)
231
+ spatial_sim = (1.0 - spatial_delta / spatial_scale).clamp(0.0, 1.0)
232
+ angle_sim = (1.0 - angle_delta / angle_scale).clamp(0.0, 1.0)
233
+ pose_scores = ((spatial_sim + angle_sim) * 0.5).max(dim=1).values
234
+
235
+ threshold = float(cfg_get(cfg, "pose_similarity_threshold", 0.6))
236
+ valid = pose_scores >= threshold
237
+ if not bool(valid.any()):
238
+ return np.empty((0,), dtype=np.int64)
239
+
240
+ valid_idx = torch.nonzero(valid, as_tuple=False).flatten()
241
+ valid_candidates = candidates_t.index_select(0, valid_idx)
242
+ valid_scores = pose_scores.index_select(0, valid_idx)
243
+
244
+ if bool(cfg_get(cfg, "training_use_plucker", False)):
245
+ plucker = _plucker_scores_tensor(
246
+ poses_t.index_select(0, valid_candidates),
247
+ target_poses,
248
+ cfg,
249
+ )
250
+ rank_scores = valid_scores + float(cfg_get(cfg, "training_plucker_weight", 0.1)) * plucker
251
+ order = torch.argsort(rank_scores, descending=True)[:count]
252
+ selected = valid_candidates.index_select(0, order).cpu().numpy()
253
+ else:
254
+ valid_np = valid_candidates.cpu().numpy()
255
+ size = min(count, len(valid_np))
256
+ choice = np.random.choice if rng is None else rng.choice
257
+ selected = choice(valid_np, size=size, replace=False)
258
+
259
+ return np.sort(np.asarray(selected, dtype=np.int64))
260
+
261
+
262
+ def _select_by_point_union(
263
+ poses: np.ndarray,
264
+ candidates: np.ndarray,
265
+ target_positions: np.ndarray,
266
+ cfg,
267
+ count: int,
268
+ *,
269
+ fov_threshold: float | None = None,
270
+ min_total_coverage: float | None = None,
271
+ use_plucker: bool = False,
272
+ ) -> np.ndarray:
273
+ if count <= 0 or len(candidates) == 0 or len(target_positions) == 0:
274
+ return np.empty((0,), dtype=np.int64)
275
+
276
+ poses_t = _as_pose_tensor(poses)
277
+ candidates = _pose_preselect(candidates.astype(np.int64), poses_t, target_positions, cfg)
278
+ inside, fov_values = _candidate_fov_masks(poses_t, candidates, target_positions, cfg)
279
+ if inside.shape[0] == 0:
280
+ return np.empty((0,), dtype=np.int64)
281
+
282
+ valid = torch.ones((len(candidates),), device=poses_t.device, dtype=torch.bool)
283
+ if fov_threshold is not None:
284
+ valid &= fov_values >= float(fov_threshold)
285
+ if not bool(valid.any()):
286
+ return np.empty((0,), dtype=np.int64)
287
+
288
+ valid_idx = torch.nonzero(valid, as_tuple=False).flatten()
289
+ candidates_t = torch.as_tensor(candidates, device=poses_t.device, dtype=torch.long).index_select(0, valid_idx)
290
+ inside = inside.index_select(0, valid_idx)
291
+ fov_values = fov_values.index_select(0, valid_idx)
292
+ target_t = torch.as_tensor(target_positions, device=poses_t.device, dtype=torch.long)
293
+ if use_plucker:
294
+ plucker = _plucker_scores_tensor(poses_t.index_select(0, candidates_t), poses_t.index_select(0, target_t), cfg)
295
+ else:
296
+ plucker = torch.zeros((candidates_t.shape[0],), device=poses_t.device, dtype=torch.float32)
297
+
298
+ remaining = torch.ones((candidates_t.shape[0],), device=poses_t.device, dtype=torch.bool)
299
+ covered = torch.zeros((inside.shape[1],), device=poses_t.device, dtype=torch.bool)
300
+ selected_rows = []
301
+ first_target = int(target_positions[0])
302
+ gaps = first_target - candidates_t
303
+
304
+ for _ in range(count):
305
+ gains = (inside & ~covered[None, :]).float().mean(dim=1)
306
+ rows = torch.nonzero(remaining, as_tuple=False).flatten()
307
+ if rows.numel() == 0:
308
+ break
309
+ row = _best_row(rows, gains, fov_values, plucker, gaps, candidates_t)
310
+ if float(gains[row].item()) <= 0.0 and float(fov_values[row].item()) <= 0.0:
311
+ break
312
+ selected_rows.append(row)
313
+ covered |= inside[row]
314
+ remaining[row] = False
315
+
316
+ if not selected_rows:
317
+ return np.empty((0,), dtype=np.int64)
318
+
319
+ coverage = float(covered.float().mean().item()) if covered.numel() else 0.0
320
+ if min_total_coverage is not None and coverage < float(min_total_coverage):
321
+ return np.empty((0,), dtype=np.int64)
322
+ selected = candidates_t.index_select(0, torch.as_tensor(selected_rows, device=poses_t.device, dtype=torch.long))
323
+ return np.sort(selected.cpu().numpy().astype(np.int64))
324
+
325
+
326
+ def _select_anchor(candidates: np.ndarray, count: int, cfg) -> np.ndarray:
327
+ if count <= 0 or len(candidates) == 0:
328
+ return np.empty((0,), dtype=np.int64)
329
+ if not bool(cfg_get(cfg, "anchor_diverse_selection", True)) or len(candidates) <= count:
330
+ return candidates[:count].astype(np.int64)
331
+
332
+ positions = np.linspace(0, len(candidates) - 1, num=count, dtype=np.int64)
333
+ selected = list(dict.fromkeys(candidates[positions].tolist()))
334
+ if len(selected) < count:
335
+ for idx in candidates:
336
+ if int(idx) not in selected:
337
+ selected.append(int(idx))
338
+ if len(selected) == count:
339
+ break
340
+ return np.asarray(selected[:count], dtype=np.int64)
341
+
342
+
343
+ def _select_dynamic(target_start: int, count: int) -> np.ndarray:
344
+ if count <= 0 or target_start <= 0:
345
+ return np.empty((0,), dtype=np.int64)
346
+ start = max(0, target_start - count)
347
+ return np.arange(start, target_start, dtype=np.int64)[-count:]
348
+
349
+
350
+ def _select_revisit(
351
+ poses: np.ndarray,
352
+ target_positions: np.ndarray,
353
+ cfg,
354
+ count: int,
355
+ excluded: np.ndarray,
356
+ split: str,
357
+ rng=None,
358
+ ) -> np.ndarray:
359
+ if count <= 0:
360
+ return np.empty((0,), dtype=np.int64)
361
+
362
+ target_start = int(target_positions[0])
363
+ exclusion = max(0, int(cfg_get(cfg, "local_context_exclusion_frames", 8)))
364
+ causal_stop = max(0, target_start - exclusion)
365
+ candidates = np.arange(0, causal_stop, dtype=np.int64)
366
+ if len(candidates) == 0:
367
+ return np.empty((0,), dtype=np.int64)
368
+
369
+ if len(excluded) > 0:
370
+ candidates = candidates[~np.isin(candidates, excluded)]
371
+ if len(candidates) == 0:
372
+ return np.empty((0,), dtype=np.int64)
373
+
374
+ if split == "training":
375
+ return _select_by_pose_similarity(poses, candidates, target_positions, cfg, count, rng=rng)
376
+
377
+ return _select_by_point_union(
378
+ poses,
379
+ candidates,
380
+ target_positions,
381
+ cfg,
382
+ count,
383
+ fov_threshold=float(cfg_get(cfg, "fov_overlap_threshold", 0.6)),
384
+ min_total_coverage=float(cfg_get(cfg, "min_total_selected_coverage", 0.1)),
385
+ use_plucker=True,
386
+ )
387
+
388
+
389
+ def select_memory_indices(poses, target_positions, cfg=None, split: str = "training", rng=None):
390
+ """Select causal memory indices and masks for [anchor][dynamic][revisit]."""
391
+
392
+ poses = _as_pose_array(poses)
393
+ target_positions = np.asarray(target_positions, dtype=np.int64)
394
+ target_positions = target_positions[(target_positions >= 0) & (target_positions < len(poses))]
395
+
396
+ counts = {
397
+ "anchor": int(cfg_get(cfg, "max_anchor_frames", 0)),
398
+ "dynamic": int(cfg_get(cfg, "max_dynamic_frames", 0)),
399
+ "revisit": int(cfg_get(cfg, "max_revisit_frames", 0)),
400
+ }
401
+ indices, masks = _empty_selection(counts)
402
+
403
+ if not bool(cfg_get(cfg, "enabled", True)) or len(target_positions) == 0:
404
+ return indices, masks
405
+
406
+ target_start = int(target_positions[0])
407
+
408
+ dynamic = _select_dynamic(target_start, counts["dynamic"])
409
+ anchor_stop = max(0, target_start - counts["dynamic"])
410
+ anchor_candidates = np.arange(0, anchor_stop, dtype=np.int64)
411
+ anchor = _select_anchor(anchor_candidates, counts["anchor"], cfg)
412
+ excluded = np.concatenate([anchor, dynamic]) if len(anchor) or len(dynamic) else np.empty((0,), dtype=np.int64)
413
+ revisit = _select_revisit(poses, target_positions, cfg, counts["revisit"], excluded, split, rng=rng)
414
+
415
+ _write_segment(indices, masks, "anchor", anchor)
416
+ _write_segment(indices, masks, "dynamic", dynamic)
417
+ _write_segment(indices, masks, "revisit", revisit)
418
+ return indices, masks
419
+
420
+
421
+ def memory_segment_lengths(target_length: int, cfg=None) -> Dict[str, int]:
422
+ return {
423
+ "target": int(target_length),
424
+ "anchor": int(cfg_get(cfg, "max_anchor_frames", 0)),
425
+ "dynamic": int(cfg_get(cfg, "max_dynamic_frames", 0)),
426
+ "revisit": int(cfg_get(cfg, "max_revisit_frames", 0)),
427
+ }
datasets/video/minecraft_video_dememwm_latent_dataset.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minecraft latent dataset with dataset-side DeMemWM memory selection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ import numpy as np
7
+ import torch
8
+ from omegaconf import DictConfig
9
+
10
+ from .memory_selection import SEGMENT_KEYS, cfg_get, memory_segment_lengths, select_memory_indices
11
+
12
+
13
+ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
14
+ """Load precomputed VAE latents and append causal memory frames.
15
+
16
+ Expected files are ``.npz`` archives under ``save_dir/<split>`` with
17
+ ``latents``, ``actions``, and ``poses`` arrays. Optional ``frame_indices``
18
+ and ``image_hw`` arrays are propagated when present.
19
+ """
20
+
21
+ def __init__(self, cfg: DictConfig, split: str = "training"):
22
+ super().__init__()
23
+ self.cfg = cfg
24
+ self.split = split
25
+ self.save_dir = Path(cfg.save_dir)
26
+ self.n_frames = int(cfg_get(cfg, "n_frames_valid", cfg.n_frames)) if split in {"validation", "test"} else int(cfg.n_frames)
27
+ self.frame_skip = int(cfg_get(cfg, "frame_skip", 1))
28
+ self.target_span = max(1, (self.n_frames - 1) * self.frame_skip + 1)
29
+ self.action_cond_dim = int(cfg_get(cfg, "action_cond_dim", 0))
30
+ self.memory_selection = cfg_get(cfg, "memory_selection", {})
31
+ self.single_eval_clip = split in {"validation", "test"} and bool(cfg_get(cfg, "single_eval_clip", True))
32
+ self._target_offsets = np.arange(self.n_frames, dtype=np.int64) * self.frame_skip
33
+ self._memory_segments = memory_segment_lengths(self.n_frames, self.memory_selection)
34
+ self.memory_condition_length = sum(self._memory_segments[key] for key in SEGMENT_KEYS)
35
+ self.data_paths = self.get_data_paths(split)
36
+ if not self.data_paths:
37
+ raise FileNotFoundError(f"No latent .npz files found for split '{split}' under {self.save_dir}")
38
+
39
+ self.lengths = np.asarray([self._file_length(path) for path in self.data_paths], dtype=np.int64)
40
+ clips = np.clip(self.lengths - self.target_span + 1, a_min=1, a_max=None).astype(np.int64)
41
+ if self.single_eval_clip:
42
+ clips = np.ones_like(clips)
43
+ self.clips_per_file = clips
44
+ self.cum_clips_per_file = np.cumsum(self.clips_per_file)
45
+ self.idx_remap = list(range(self.__len__()))
46
+ if split == "training" and bool(cfg_get(cfg, "shuffle_clips", True)):
47
+ rng = np.random.default_rng(int(cfg_get(cfg, "seed", 0)))
48
+ rng.shuffle(self.idx_remap)
49
+
50
+ def get_data_paths(self, split: str):
51
+ split_dir = self.save_dir / split
52
+ data_dir = split_dir if split_dir.exists() else self.save_dir
53
+ return sorted(path for path in data_dir.glob("**/*.npz") if path.is_file())
54
+
55
+ def __len__(self):
56
+ return int(self.cum_clips_per_file[-1])
57
+
58
+ def split_idx(self, idx: int):
59
+ file_idx = int(np.argmax(self.cum_clips_per_file > idx))
60
+ prev = int(np.pad(self.cum_clips_per_file, (1, 0))[file_idx])
61
+ return file_idx, int(idx - prev)
62
+
63
+ def __getitem__(self, idx: int):
64
+ return self.load_data(self.idx_remap[idx])
65
+
66
+ def load_data(self, idx: int):
67
+ # === 1. Resolve clip and load arrays ===
68
+ file_idx, frame_idx = self.split_idx(int(idx))
69
+ arrays = self._load_arrays(self.data_paths[file_idx])
70
+ latents = arrays["latents"]
71
+ actions = arrays["actions"]
72
+ poses_pool = arrays["poses"]
73
+ frame_ids = arrays["frame_indices"]
74
+
75
+ # === 2. Select target and memory indices ===
76
+ target_positions = frame_idx + self._target_offsets
77
+ memory_indices, memory_masks = select_memory_indices(
78
+ poses_pool,
79
+ target_positions,
80
+ self.memory_selection,
81
+ split=self.split,
82
+ )
83
+ ordered_memory = [memory_indices[key] for key in SEGMENT_KEYS]
84
+ source_positions = np.concatenate([target_positions, *ordered_memory]).astype(np.int64)
85
+
86
+ # === 3. Gather arrays and normalize poses ===
87
+ latent_seq, valid_mask = self._gather_array(latents, source_positions)
88
+ action_seq, _ = self._gather_array(actions, source_positions)
89
+ pose_seq, _ = self._gather_array(poses_pool, source_positions)
90
+ frame_index_seq = self._gather_frame_indices(frame_ids, source_positions)
91
+
92
+ target_valid = valid_mask[: self.n_frames]
93
+ if target_valid.any():
94
+ ref_idx = int(target_positions[target_valid][0])
95
+ else:
96
+ ref_idx = min(max(frame_idx, 0), len(poses_pool) - 1)
97
+ pose_seq = self._normalize_poses(pose_seq, poses_pool[ref_idx])
98
+
99
+ masks = {"target": torch.as_tensor(target_valid, dtype=torch.bool)}
100
+ for key in SEGMENT_KEYS:
101
+ masks[key] = torch.as_tensor(memory_masks[key], dtype=torch.bool)
102
+
103
+ # === 4. Return all items ===
104
+ return {
105
+ "latents": torch.as_tensor(latent_seq).float(),
106
+ "actions": torch.as_tensor(action_seq).float(),
107
+ "poses": torch.as_tensor(pose_seq).float(),
108
+ "frame_indices": torch.as_tensor(frame_index_seq, dtype=torch.long),
109
+ "memory_segments": dict(self._memory_segments),
110
+ "memory_masks": masks,
111
+ "image_hw": torch.as_tensor(arrays["image_hw"], dtype=torch.long),
112
+ }
113
+
114
+ def _file_length(self, path: Path) -> int:
115
+ with np.load(path, allow_pickle=False) as data:
116
+ return int(data["latents"].shape[0])
117
+
118
+ def _load_arrays(self, path: Path):
119
+ with np.load(path, allow_pickle=False) as data:
120
+ latents = np.asarray(data["latents"])
121
+ actions = np.asarray(data["actions"])
122
+ poses = np.asarray(data["poses"], dtype=np.float32)
123
+ if "frame_indices" in data:
124
+ frame_indices = np.asarray(data["frame_indices"], dtype=np.int64)
125
+ else:
126
+ frame_indices = np.arange(len(latents), dtype=np.int64)
127
+
128
+ if "image_hw" in data:
129
+ image_hw = np.asarray(data["image_hw"], dtype=np.int64).reshape(-1)[:2]
130
+ else:
131
+ image_hw = np.asarray(cfg_get(self.cfg, "image_hw", [cfg_get(self.cfg, "resolution", 0), cfg_get(self.cfg, "resolution", 0)]), dtype=np.int64)
132
+
133
+ self._validate_arrays(path, latents, actions, poses, frame_indices)
134
+ return {
135
+ "latents": latents,
136
+ "actions": actions,
137
+ "poses": poses,
138
+ "frame_indices": frame_indices,
139
+ "image_hw": image_hw,
140
+ }
141
+
142
+ def _validate_arrays(self, path: Path, latents, actions, poses, frame_indices) -> None:
143
+ length = len(latents)
144
+ if len(actions) != length or len(poses) != length or len(frame_indices) != length:
145
+ raise ValueError(f"Latents/actions/poses/frame_indices length mismatch in {path}")
146
+ if poses.ndim != 2 or poses.shape[1] < 5:
147
+ raise ValueError(f"poses in {path} must have shape [num_frames, >=5]")
148
+ if self.action_cond_dim and actions.ndim == 2 and actions.shape[1] != self.action_cond_dim:
149
+ raise ValueError(
150
+ f"actions in {path} have dim {actions.shape[1]}, expected {self.action_cond_dim}; "
151
+ "precompute or configure the latent action dimension explicitly"
152
+ )
153
+
154
+ @staticmethod
155
+ def _gather_array(array: np.ndarray, indices: np.ndarray):
156
+ output = np.zeros((len(indices), *array.shape[1:]), dtype=array.dtype)
157
+ valid = (indices >= 0) & (indices < len(array))
158
+ if valid.any():
159
+ output[valid] = array[indices[valid]]
160
+ return output, valid
161
+
162
+ @staticmethod
163
+ def _gather_frame_indices(frame_ids: np.ndarray, indices: np.ndarray):
164
+ output = np.full((len(indices),), -1, dtype=np.int64)
165
+ valid = (indices >= 0) & (indices < len(frame_ids))
166
+ if valid.any():
167
+ output[valid] = frame_ids[indices[valid]]
168
+ return output
169
+
170
+ @staticmethod
171
+ def _normalize_poses(poses: np.ndarray, ref_pose: np.ndarray):
172
+ poses = poses.astype(np.float32, copy=True)
173
+ poses[:, :3] -= ref_pose[:3]
174
+ poses[:, -1] = -poses[:, -1]
175
+ poses[:, 3:] %= 360.0
176
+ return poses
experiments/exp_video.py CHANGED
@@ -1,8 +1,10 @@
1
  from datasets.video import (
2
- MinecraftVideoDataset
 
3
  )
4
 
5
  from algorithms.worldmem import WorldMemMinecraft
 
6
  from .exp_base import BaseLightningExperiment
7
 
8
 
@@ -13,9 +15,12 @@ class VideoPredictionExperiment(BaseLightningExperiment):
13
 
14
  compatible_algorithms = dict(
15
  df_video_worldmemminecraft=WorldMemMinecraft,
 
16
  )
17
 
18
  compatible_datasets = dict(
19
  # video datasets
20
  video_minecraft=MinecraftVideoDataset,
 
21
  )
 
 
1
  from datasets.video import (
2
+ MinecraftVideoDataset,
3
+ MinecraftVideoDeMemWMLatentDataset,
4
  )
5
 
6
  from algorithms.worldmem import WorldMemMinecraft
7
+ from algorithms.dememwm import DeMemWMMinecraft
8
  from .exp_base import BaseLightningExperiment
9
 
10
 
 
15
 
16
  compatible_algorithms = dict(
17
  df_video_worldmemminecraft=WorldMemMinecraft,
18
+ dememwm_base=DeMemWMMinecraft,
19
  )
20
 
21
  compatible_datasets = dict(
22
  # video datasets
23
  video_minecraft=MinecraftVideoDataset,
24
+ video_minecraft_dememwm_latent=MinecraftVideoDeMemWMLatentDataset,
25
  )
26
+
tests/test_dememwm_latent_dataset.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tempfile
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ from omegaconf import OmegaConf
7
+
8
+ from datasets.video.memory_selection import select_memory_indices
9
+ from datasets.video.minecraft_video_dememwm_latent_dataset import MinecraftVideoDeMemWMLatentDataset
10
+
11
+
12
+ def _selection_cfg(**overrides):
13
+ cfg = {
14
+ "enabled": True,
15
+ "max_anchor_frames": 2,
16
+ "max_dynamic_frames": 2,
17
+ "max_revisit_frames": 2,
18
+ "pose_similarity_threshold": 0.6,
19
+ "training_use_plucker": False,
20
+ "training_plucker_weight": 0.1,
21
+ "fov_overlap_threshold": 0.0,
22
+ "min_total_selected_coverage": 0.0,
23
+ "local_context_exclusion_frames": 2,
24
+ "plucker_moment_radius": 30.0,
25
+ "anchor_diverse_selection": False,
26
+ "fov_num_points": 512,
27
+ "fov_sample_seed": 0,
28
+ "pose_preselect_topk": 64,
29
+ "candidate_chunk_size": 64,
30
+ }
31
+ cfg.update(overrides)
32
+ return OmegaConf.create(cfg)
33
+
34
+
35
+ def _poses(num_frames):
36
+ poses = np.zeros((num_frames, 5), dtype=np.float32)
37
+ poses[:, 0] = np.arange(num_frames, dtype=np.float32)
38
+ poses[:, 4] = np.arange(num_frames, dtype=np.float32) * 3.0
39
+ return poses
40
+
41
+
42
+ class MemorySelectionTests(unittest.TestCase):
43
+ def test_revisit_uses_sampled_fov_selection(self):
44
+ poses = np.array(
45
+ [
46
+ [0, 0, 0, 0, 0],
47
+ [0, 0, 0, 0, 180],
48
+ [0, 0, 0, 0, 180],
49
+ [0, 0, 0, 0, 180],
50
+ [0, 0, 0, 0, 180],
51
+ [0, 0, 0, 0, 0],
52
+ ],
53
+ dtype=np.float32,
54
+ )
55
+ cfg = _selection_cfg(
56
+ max_anchor_frames=0,
57
+ max_dynamic_frames=0,
58
+ max_revisit_frames=1,
59
+ fov_overlap_threshold=0.5,
60
+ local_context_exclusion_frames=1,
61
+ )
62
+ indices, masks = select_memory_indices(poses, np.array([5]), cfg, split="validation")
63
+
64
+ self.assertEqual(indices["revisit"].tolist(), [0])
65
+ self.assertEqual(masks["revisit"].tolist(), [True])
66
+
67
+ def test_training_revisit_uses_pose_similarity_threshold(self):
68
+ poses = np.array(
69
+ [
70
+ [0.2, 0, 0, 0, 2],
71
+ [40, 0, 0, 0, 180],
72
+ [45, 0, 0, 0, 180],
73
+ [50, 0, 0, 0, 180],
74
+ [55, 0, 0, 0, 180],
75
+ [0, 0, 0, 0, 0],
76
+ ],
77
+ dtype=np.float32,
78
+ )
79
+ cfg = _selection_cfg(
80
+ max_anchor_frames=0,
81
+ max_dynamic_frames=0,
82
+ max_revisit_frames=1,
83
+ pose_similarity_threshold=0.6,
84
+ local_context_exclusion_frames=1,
85
+ )
86
+ indices, masks = select_memory_indices(
87
+ poses,
88
+ np.array([5]),
89
+ cfg,
90
+ split="training",
91
+ rng=np.random.default_rng(0),
92
+ )
93
+
94
+ self.assertEqual(indices["revisit"].tolist(), [0])
95
+ self.assertEqual(masks["revisit"].tolist(), [True])
96
+
97
+ def test_training_revisit_pads_when_pose_matches_are_short(self):
98
+ poses = np.array(
99
+ [
100
+ [0.2, 0, 0, 0, 2],
101
+ [40, 0, 0, 0, 180],
102
+ [45, 0, 0, 0, 180],
103
+ [50, 0, 0, 0, 180],
104
+ [55, 0, 0, 0, 180],
105
+ [0, 0, 0, 0, 0],
106
+ ],
107
+ dtype=np.float32,
108
+ )
109
+ cfg = _selection_cfg(
110
+ max_anchor_frames=0,
111
+ max_dynamic_frames=0,
112
+ max_revisit_frames=2,
113
+ pose_similarity_threshold=0.95,
114
+ local_context_exclusion_frames=1,
115
+ )
116
+ indices, masks = select_memory_indices(
117
+ poses,
118
+ np.array([5]),
119
+ cfg,
120
+ split="training",
121
+ rng=np.random.default_rng(0),
122
+ )
123
+
124
+ self.assertEqual(indices["revisit"].tolist(), [0, -1])
125
+ self.assertEqual(masks["revisit"].tolist(), [True, False])
126
+
127
+ def test_dememwm_selection_is_typed_and_causal(self):
128
+ poses = _poses(12)
129
+ indices, masks = select_memory_indices(poses, np.array([6, 7, 8]), _selection_cfg())
130
+
131
+ self.assertEqual(indices["anchor"].tolist(), [0, 1])
132
+ self.assertEqual(indices["dynamic"].tolist(), [4, 5])
133
+ self.assertTrue(np.all(indices["revisit"][masks["revisit"]] < 4))
134
+ for key in ("anchor", "dynamic", "revisit"):
135
+ self.assertTrue(np.all(indices[key][masks[key]] < 6))
136
+ selected = np.concatenate([indices[key][masks[key]] for key in ("anchor", "dynamic", "revisit")])
137
+ self.assertEqual(len(selected), len(np.unique(selected)))
138
+
139
+
140
+ class DeMemWMLatentDatasetTests(unittest.TestCase):
141
+ def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
142
+ with tempfile.TemporaryDirectory() as tmp:
143
+ root = Path(tmp)
144
+ split_dir = root / "training"
145
+ split_dir.mkdir()
146
+ num_frames = 12
147
+ np.savez(
148
+ split_dir / "sample.npz",
149
+ latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
150
+ actions=np.ones((num_frames, 25), dtype=np.float32),
151
+ poses=_poses(num_frames),
152
+ frame_indices=np.arange(100, 100 + num_frames, dtype=np.int64),
153
+ image_hw=np.array([360, 640], dtype=np.int64),
154
+ )
155
+ cfg = OmegaConf.create(
156
+ {
157
+ "save_dir": str(root),
158
+ "n_frames": 3,
159
+ "n_frames_valid": 3,
160
+ "frame_skip": 1,
161
+ "action_cond_dim": 25,
162
+ "resolution": 128,
163
+ "image_hw": [360, 640],
164
+ "shuffle_clips": False,
165
+ "single_eval_clip": True,
166
+ "memory_selection": _selection_cfg(),
167
+ }
168
+ )
169
+ dataset = MinecraftVideoDeMemWMLatentDataset(cfg, split="training")
170
+ sample = dataset[6]
171
+
172
+ self.assertEqual(sample["memory_segments"], {"target": 3, "anchor": 2, "dynamic": 2, "revisit": 2})
173
+ self.assertEqual(tuple(sample["latents"].shape), (9, 1, 1, 2))
174
+ self.assertEqual(sample["frame_indices"][:3].tolist(), [106, 107, 108])
175
+ self.assertEqual(sample["frame_indices"][3:5].tolist(), [100, 101])
176
+ self.assertEqual(sample["frame_indices"][5:7].tolist(), [104, 105])
177
+ self.assertTrue(all(idx < 104 for idx in sample["frame_indices"][7:9].tolist() if idx >= 0))
178
+ self.assertEqual(len(sample["frame_indices"][7:9]), 2)
179
+ self.assertTrue(sample["memory_masks"]["target"].all().item())
180
+ self.assertTrue(sample["memory_masks"]["anchor"].all().item())
181
+ self.assertTrue(sample["memory_masks"]["dynamic"].all().item())
182
+ self.assertTrue(sample["memory_masks"]["revisit"].all().item())
183
+ self.assertEqual(sample["image_hw"].tolist(), [360, 640])
184
+
185
+
186
+ if __name__ == "__main__":
187
+ unittest.main()