Add DeMemWM step 1-4 dataset scaffold
Browse files- .exp_artifact/dememwm_rebuild_plan.md +290 -0
- algorithms/dememwm/__init__.py +37 -0
- algorithms/dememwm/df_base.py +307 -0
- algorithms/dememwm/df_video.py +954 -0
- algorithms/dememwm/models/attention.py +342 -0
- algorithms/dememwm/models/cameractrl_module.py +12 -0
- algorithms/dememwm/models/diffusion.py +520 -0
- algorithms/dememwm/models/dit.py +572 -0
- algorithms/dememwm/models/pose_prediction.py +42 -0
- algorithms/dememwm/models/rotary_embedding_torch.py +302 -0
- algorithms/dememwm/models/utils.py +163 -0
- algorithms/dememwm/models/vae.py +359 -0
- algorithms/dememwm/pose_prediction.py +374 -0
- configurations/algorithm/dememwm_base.yaml +48 -0
- configurations/dataset/video_minecraft_dememwm_latent.yaml +33 -0
- datasets/video/__init__.py +2 -1
- datasets/video/memory_selection.py +427 -0
- datasets/video/minecraft_video_dememwm_latent_dataset.py +176 -0
- experiments/exp_video.py +6 -1
- tests/test_dememwm_latent_dataset.py +187 -0
.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()
|