xizaoqu commited on
Commit ·
622d9c9
1
Parent(s): a0e6519
update trainning pipeline and datasets
Browse files- README.md +73 -2
- app.py +33 -5
- configurations/algorithm/base_algo.yaml +3 -0
- configurations/algorithm/base_pytorch_algo.yaml +4 -0
- configurations/algorithm/df_base.yaml +42 -0
- configurations/algorithm/df_video_worldmemminecraft.yaml +43 -0
- configurations/dataset/base_dataset.yaml +3 -0
- configurations/dataset/base_video.yaml +14 -0
- configurations/dataset/video_minecraft.yaml +14 -0
- configurations/experiment/base_experiment.yaml +2 -0
- configurations/experiment/base_pytorch.yaml +50 -0
- configurations/experiment/exp_video.yaml +31 -0
- configurations/huggingface.yaml +1 -1
- configurations/training.yaml +16 -0
- datasets/README.md +11 -0
- datasets/__init__.py +1 -0
- datasets/video/__init__.py +1 -0
- datasets/video/base_video_dataset.py +153 -0
- datasets/video/minecraft_video_dataset.py +250 -0
- infer.sh +22 -0
- train_stage_1.sh +29 -0
- train_stage_2.sh +25 -0
- train_stage_3.sh +24 -0
README.md
CHANGED
|
@@ -39,6 +39,7 @@ https://github.com/user-attachments/assets/fb8a32e2-9470-4819-a93d-c38caf76d72c
|
|
| 39 |
conda create python=3.10 -n worldmem
|
| 40 |
conda activate worldmem
|
| 41 |
pip install -r requirements.txt
|
|
|
|
| 42 |
```
|
| 43 |
|
| 44 |
|
|
@@ -48,11 +49,81 @@ pip install -r requirements.txt
|
|
| 48 |
python app.py
|
| 49 |
```
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
## TODO
|
| 52 |
|
| 53 |
- [x] Release inference models and weights;
|
| 54 |
-
- [
|
| 55 |
-
- [
|
| 56 |
|
| 57 |
|
| 58 |
|
|
|
|
| 39 |
conda create python=3.10 -n worldmem
|
| 40 |
conda activate worldmem
|
| 41 |
pip install -r requirements.txt
|
| 42 |
+
conda install -c conda-forge ffmpeg=4.3.2
|
| 43 |
```
|
| 44 |
|
| 45 |
|
|
|
|
| 49 |
python app.py
|
| 50 |
```
|
| 51 |
|
| 52 |
+
## Training and Inference
|
| 53 |
+
|
| 54 |
+
To enable cloud logging with [Weights & Biases (wandb)](https://wandb.ai/site), follow these steps:
|
| 55 |
+
|
| 56 |
+
1. Sign up for a wandb account.
|
| 57 |
+
2. Run the following command to log in:
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
wandb login
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
3. Open `configurations/training.yaml` and set the `entity` and `project` field to your wandb username.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
### Training
|
| 68 |
+
|
| 69 |
+
We observe that gradually increasing task difficulty improves performance. Thus, we adopt a multi-stage training strategy:
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
sh train_stage_1.sh # Small range, no vertical turning
|
| 73 |
+
sh train_stage_2.sh # Large range, no vertical turning
|
| 74 |
+
sh train_stage_3.sh # Large range, with vertical turning
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
To resume training from a previous checkpoint, configure the `resume` and `output_dir` variables in the corresponding `.sh` script.
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
### Inference
|
| 82 |
+
|
| 83 |
+
To run inference:
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
sh infer.sh
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
You can either **load the diffusion model and VAE separately**:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
+diffusion_model_path=yslan/worldmem_checkpoints/diffusion_only.ckpt \
|
| 93 |
+
+vae_path=yslan/worldmem_checkpoints/vae_only.ckpt \
|
| 94 |
+
+customized_load=true \
|
| 95 |
+
+seperate_load=true \
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Or **load a combined checkpoint**:
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
+load=your_model_path \
|
| 102 |
+
+customized_load=true \
|
| 103 |
+
+seperate_load=false \
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
## Dataset
|
| 109 |
+
|
| 110 |
+
Download the Minecraft dataset from [Hugging Face](https://huggingface.co/datasets/zeqixiao/worldmem_minecraft_dataset)
|
| 111 |
+
|
| 112 |
+
Place the dataset in the following directory structure:
|
| 113 |
+
|
| 114 |
+
```
|
| 115 |
+
data/
|
| 116 |
+
└── minecraft/
|
| 117 |
+
├── training/
|
| 118 |
+
└── validation/
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
|
| 122 |
## TODO
|
| 123 |
|
| 124 |
- [x] Release inference models and weights;
|
| 125 |
+
- [x] Release training pipeline on Minecraft;
|
| 126 |
+
- [x] Release training data on Minecraft;
|
| 127 |
|
| 128 |
|
| 129 |
|
app.py
CHANGED
|
@@ -23,11 +23,23 @@ from huggingface_hub import hf_hub_download
|
|
| 23 |
import tempfile
|
| 24 |
import os
|
| 25 |
import requests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
torch.set_float32_matmul_precision("high")
|
| 28 |
|
| 29 |
def load_custom_checkpoint(algo, checkpoint_path):
|
| 30 |
-
|
| 31 |
hf_ckpt = str(checkpoint_path).split('/')
|
| 32 |
repo_id = '/'.join(hf_ckpt[:2])
|
| 33 |
file_name = '/'.join(hf_ckpt[2:])
|
|
@@ -35,9 +47,17 @@ def load_custom_checkpoint(algo, checkpoint_path):
|
|
| 35 |
filename=file_name)
|
| 36 |
ckpt = torch.load(model_path, map_location=torch.device('cpu'))
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
print("Load: ", model_path)
|
| 40 |
-
|
| 41 |
ckpt = torch.load(checkpoint_path, map_location=torch.device('cpu'))
|
| 42 |
|
| 43 |
filtered_state_dict = {}
|
|
@@ -192,13 +212,21 @@ SUNFLOWERS_RAIN_IMAGE = "assets/rain_sunflower_plains.png"
|
|
| 192 |
device = torch.device('cuda')
|
| 193 |
|
| 194 |
def save_video(frames, path="output.mp4", fps=10):
|
|
|
|
| 195 |
h, w, _ = frames[0].shape
|
| 196 |
-
|
|
|
|
| 197 |
for frame in frames:
|
| 198 |
out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
|
| 199 |
out.release()
|
| 200 |
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
cfg = OmegaConf.load("configurations/huggingface.yaml")
|
| 204 |
worldmem = WorldMemMinecraft(cfg)
|
|
|
|
| 23 |
import tempfile
|
| 24 |
import os
|
| 25 |
import requests
|
| 26 |
+
from huggingface_hub import model_info
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def is_huggingface_model(path: str) -> bool:
|
| 30 |
+
hf_ckpt = str(path).split('/')
|
| 31 |
+
repo_id = '/'.join(hf_ckpt[:2])
|
| 32 |
+
try:
|
| 33 |
+
model_info(repo_id)
|
| 34 |
+
return True
|
| 35 |
+
except:
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
|
| 39 |
torch.set_float32_matmul_precision("high")
|
| 40 |
|
| 41 |
def load_custom_checkpoint(algo, checkpoint_path):
|
| 42 |
+
if is_huggingface_model(str(checkpoint_path)):
|
| 43 |
hf_ckpt = str(checkpoint_path).split('/')
|
| 44 |
repo_id = '/'.join(hf_ckpt[:2])
|
| 45 |
file_name = '/'.join(hf_ckpt[2:])
|
|
|
|
| 47 |
filename=file_name)
|
| 48 |
ckpt = torch.load(model_path, map_location=torch.device('cpu'))
|
| 49 |
|
| 50 |
+
filtered_state_dict = {}
|
| 51 |
+
for k, v in ckpt['state_dict'].items():
|
| 52 |
+
if "frame_timestep_embedder" in k:
|
| 53 |
+
new_k = k.replace("frame_timestep_embedder", "timestamp_embedding")
|
| 54 |
+
filtered_state_dict[new_k] = v
|
| 55 |
+
else:
|
| 56 |
+
filtered_state_dict[k] = v
|
| 57 |
+
|
| 58 |
+
algo.load_state_dict(filtered_state_dict, strict=True)
|
| 59 |
print("Load: ", model_path)
|
| 60 |
+
else:
|
| 61 |
ckpt = torch.load(checkpoint_path, map_location=torch.device('cpu'))
|
| 62 |
|
| 63 |
filtered_state_dict = {}
|
|
|
|
| 212 |
device = torch.device('cuda')
|
| 213 |
|
| 214 |
def save_video(frames, path="output.mp4", fps=10):
|
| 215 |
+
temp_path = path[:-4] + "_temp.mp4"
|
| 216 |
h, w, _ = frames[0].shape
|
| 217 |
+
|
| 218 |
+
out = cv2.VideoWriter(temp_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h))
|
| 219 |
for frame in frames:
|
| 220 |
out.write(cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
|
| 221 |
out.release()
|
| 222 |
|
| 223 |
+
ffmpeg_cmd = [
|
| 224 |
+
"ffmpeg", "-y", "-i", temp_path,
|
| 225 |
+
"-c:v", "libx264", "-crf", "23", "-preset", "medium",
|
| 226 |
+
path
|
| 227 |
+
]
|
| 228 |
+
subprocess.run(ffmpeg_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
| 229 |
+
os.remove(temp_path)
|
| 230 |
|
| 231 |
cfg = OmegaConf.load("configurations/huggingface.yaml")
|
| 232 |
worldmem = WorldMemMinecraft(cfg)
|
configurations/algorithm/base_algo.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This will be passed as the cfg to Algo.__init__(cfg) of your algorithm class
|
| 2 |
+
|
| 3 |
+
debug: ${debug} # inherited from configurations/config.yaml
|
configurations/algorithm/base_pytorch_algo.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base_algo # inherits from configurations/algorithm/base_algo.yaml
|
| 3 |
+
|
| 4 |
+
lr: ${experiment.training.lr}
|
configurations/algorithm/df_base.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base_pytorch_algo
|
| 3 |
+
|
| 4 |
+
# dataset-dependent configurations
|
| 5 |
+
x_shape: ${dataset.observation_shape}
|
| 6 |
+
frame_stack: 1
|
| 7 |
+
frame_skip: 1
|
| 8 |
+
data_mean: ${dataset.data_mean}
|
| 9 |
+
data_std: ${dataset.data_std}
|
| 10 |
+
external_cond_dim: 0 #${dataset.action_dim}
|
| 11 |
+
context_frames: ${dataset.context_length}
|
| 12 |
+
# training hyperparameters
|
| 13 |
+
weight_decay: 1e-4
|
| 14 |
+
warmup_steps: 10000
|
| 15 |
+
optimizer_beta: [0.9, 0.999]
|
| 16 |
+
# diffusion-related
|
| 17 |
+
uncertainty_scale: 1
|
| 18 |
+
guidance_scale: 0.0
|
| 19 |
+
chunk_size: 1 # -1 for full trajectory diffusion, number to specify diffusion chunk size
|
| 20 |
+
scheduling_matrix: autoregressive
|
| 21 |
+
noise_level: random_all
|
| 22 |
+
causal: True
|
| 23 |
+
|
| 24 |
+
diffusion:
|
| 25 |
+
# training
|
| 26 |
+
objective: pred_x0
|
| 27 |
+
beta_schedule: cosine
|
| 28 |
+
schedule_fn_kwargs: {}
|
| 29 |
+
clip_noise: 20.0
|
| 30 |
+
use_snr: False
|
| 31 |
+
use_cum_snr: False
|
| 32 |
+
use_fused_snr: False
|
| 33 |
+
snr_clip: 5.0
|
| 34 |
+
cum_snr_decay: 0.98
|
| 35 |
+
timesteps: 1000
|
| 36 |
+
# sampling
|
| 37 |
+
sampling_timesteps: 50 # fixme, numer of diffusion steps, should be increased
|
| 38 |
+
ddim_sampling_eta: 1.0
|
| 39 |
+
stabilization_level: 10
|
| 40 |
+
# architecture
|
| 41 |
+
architecture:
|
| 42 |
+
network_size: 64
|
configurations/algorithm/df_video_worldmemminecraft.yaml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- df_base
|
| 3 |
+
|
| 4 |
+
n_frames: ${dataset.n_frames}
|
| 5 |
+
frame_skip: ${dataset.frame_skip}
|
| 6 |
+
metadata: ${dataset.metadata}
|
| 7 |
+
|
| 8 |
+
# training hyperparameters
|
| 9 |
+
weight_decay: 2e-3
|
| 10 |
+
warmup_steps: 1000
|
| 11 |
+
optimizer_beta: [0.9, 0.99]
|
| 12 |
+
action_cond_dim: 25
|
| 13 |
+
use_plucker: true
|
| 14 |
+
|
| 15 |
+
diffusion:
|
| 16 |
+
# training
|
| 17 |
+
beta_schedule: sigmoid
|
| 18 |
+
objective: pred_v
|
| 19 |
+
use_fused_snr: True
|
| 20 |
+
cum_snr_decay: 0.96
|
| 21 |
+
clip_noise: 20.
|
| 22 |
+
# sampling
|
| 23 |
+
sampling_timesteps: 20
|
| 24 |
+
ddim_sampling_eta: 0.0
|
| 25 |
+
stabilization_level: 15
|
| 26 |
+
# architecture
|
| 27 |
+
architecture:
|
| 28 |
+
network_size: 64
|
| 29 |
+
attn_heads: 4
|
| 30 |
+
attn_dim_head: 64
|
| 31 |
+
dim_mults: [1, 2, 4, 8]
|
| 32 |
+
resolution: ${dataset.resolution}
|
| 33 |
+
attn_resolutions: [16, 32, 64, 128]
|
| 34 |
+
use_init_temporal_attn: True
|
| 35 |
+
use_linear_attn: True
|
| 36 |
+
time_emb_type: rotary
|
| 37 |
+
|
| 38 |
+
metrics:
|
| 39 |
+
# - fvd
|
| 40 |
+
# - fid
|
| 41 |
+
# - lpips
|
| 42 |
+
|
| 43 |
+
_name: df_video_worldmemminecraft
|
configurations/dataset/base_dataset.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This will be passed as the cfg to Dataset.__init__(cfg) of your dataset class
|
| 2 |
+
|
| 3 |
+
debug: ${debug} # inherited from configurations/config.yaml
|
configurations/dataset/base_video.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base_dataset
|
| 3 |
+
|
| 4 |
+
metadata: "data/${dataset.name}/metadata.json"
|
| 5 |
+
data_mean: "data/${dataset.name}/data_mean.npy"
|
| 6 |
+
data_std: "data/${dataset.name}/data_std.npy"
|
| 7 |
+
save_dir: ???
|
| 8 |
+
n_frames: 32
|
| 9 |
+
context_length: 4
|
| 10 |
+
resolution: 128
|
| 11 |
+
observation_shape: [3, "${dataset.resolution}", "${dataset.resolution}"]
|
| 12 |
+
external_cond_dim: 0
|
| 13 |
+
validation_multiplier: 1
|
| 14 |
+
frame_skip: 1
|
configurations/dataset/video_minecraft.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base_video
|
| 3 |
+
|
| 4 |
+
save_dir: data/minecraft_simple_backforward
|
| 5 |
+
n_frames: 16 # TODO: increase later
|
| 6 |
+
resolution: 128
|
| 7 |
+
data_mean: 0.5
|
| 8 |
+
data_std: 0.5
|
| 9 |
+
action_cond_dim: 25
|
| 10 |
+
context_length: 1
|
| 11 |
+
frame_skip: 1
|
| 12 |
+
validation_multiplier: 1
|
| 13 |
+
|
| 14 |
+
_name: video_minecraft_oasis
|
configurations/experiment/base_experiment.yaml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
debug: ${debug} # inherited from configurations/config.yaml
|
| 2 |
+
tasks: [main] # tasks to run sequantially, such as [training, test], useful when your project has multiple stages and you want to run only a subset of them.
|
configurations/experiment/base_pytorch.yaml
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# inherites from base_experiment.yaml
|
| 2 |
+
# most of the options have docs at https://lightning.ai/docs/pytorch/stable/common/trainer.html
|
| 3 |
+
|
| 4 |
+
defaults:
|
| 5 |
+
- base_experiment
|
| 6 |
+
|
| 7 |
+
tasks: [training] # tasks to run sequantially, change when your project has multiple stages and you want to run only a subset of them.
|
| 8 |
+
num_nodes: 1 # number of gpu servers used in large scale distributed training
|
| 9 |
+
|
| 10 |
+
training:
|
| 11 |
+
precision: 16-mixed # set float precision, 16-mixed is faster while 32 is more stable
|
| 12 |
+
compile: False # whether to compile the model with torch.compile
|
| 13 |
+
lr: 0.001 # learning rate
|
| 14 |
+
batch_size: 16 # training batch size; effective batch size is this number * gpu * nodes iff using distributed training
|
| 15 |
+
max_epochs: 1000 # set to -1 to train forever
|
| 16 |
+
max_steps: -1 # set to -1 to train forever, will override max_epochs
|
| 17 |
+
max_time: null # set to something like "00:12:00:00" to enable
|
| 18 |
+
data:
|
| 19 |
+
num_workers: 4 # number of CPU threads for data preprocessing.
|
| 20 |
+
shuffle: True # whether training data will be shuffled
|
| 21 |
+
optim:
|
| 22 |
+
accumulate_grad_batches: 1 # accumulate gradients for n batches before backprop
|
| 23 |
+
gradient_clip_val: 0 # clip gradients with norm above this value, set to 0 to disable
|
| 24 |
+
checkpointing:
|
| 25 |
+
# these are arguments to pytorch lightning's callback, `ModelCheckpoint` class
|
| 26 |
+
every_n_train_steps: 5000 # save a checkpoint every n train steps
|
| 27 |
+
every_n_epochs: null # mutually exclusive with ``every_n_train_steps`` and ``train_time_interval``
|
| 28 |
+
train_time_interval: null # in format of "00:12:00:00", mutually exclusive with ``every_n_train_steps`` and ``every_n_epochs``.
|
| 29 |
+
enable_version_counter: False # If this is ``False``, later checkpoint will be overwrite previous ones.
|
| 30 |
+
|
| 31 |
+
validation:
|
| 32 |
+
precision: 16-mixed
|
| 33 |
+
compile: False # whether to compile the model with torch.compile
|
| 34 |
+
batch_size: 16 # validation batch size per GPU; effective batch size is this number * gpu * nodes iff using distributed training
|
| 35 |
+
val_every_n_step: 2000 # controls how frequent do we run validation, can be float (fraction of epoches) or int (steps) or null (if val_every_n_epoch is set)
|
| 36 |
+
val_every_n_epoch: null # if you want to do validation every n epoches, requires val_every_n_step to be null.
|
| 37 |
+
limit_batch: null # if null, run through validation set. Otherwise limit the number of batches to use for validation.
|
| 38 |
+
inference_mode: True # whether to run validation in inference mode (enable_grad won't work!)
|
| 39 |
+
data:
|
| 40 |
+
num_workers: 4 # number of CPU threads for data preprocessing, for validation.
|
| 41 |
+
shuffle: False # whether validation data will be shuffled
|
| 42 |
+
|
| 43 |
+
test:
|
| 44 |
+
precision: 16-mixed
|
| 45 |
+
compile: False # whether to compile the model with torch.compile
|
| 46 |
+
batch_size: 4 # test batch size per GPU; effective batch size is this number * gpu * nodes iff using distributed training
|
| 47 |
+
limit_batch: null # if null, run through test set. Otherwise limit the number of batches to use for test.
|
| 48 |
+
data:
|
| 49 |
+
num_workers: 4 # number of CPU threads for data preprocessing, for test.
|
| 50 |
+
shuffle: False # whether test data will be shuffled
|
configurations/experiment/exp_video.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- base_pytorch
|
| 3 |
+
|
| 4 |
+
tasks: [training]
|
| 5 |
+
|
| 6 |
+
training:
|
| 7 |
+
lr: 2e-5
|
| 8 |
+
precision: 16-mixed
|
| 9 |
+
batch_size: 4
|
| 10 |
+
max_epochs: -1
|
| 11 |
+
max_steps: 2000005
|
| 12 |
+
checkpointing:
|
| 13 |
+
every_n_train_steps: 2500
|
| 14 |
+
optim:
|
| 15 |
+
gradient_clip_val: 1.0
|
| 16 |
+
|
| 17 |
+
validation:
|
| 18 |
+
val_every_n_step: 2500
|
| 19 |
+
val_every_n_epoch: null
|
| 20 |
+
batch_size: 4
|
| 21 |
+
limit_batch: 1
|
| 22 |
+
|
| 23 |
+
test:
|
| 24 |
+
limit_batch: 1
|
| 25 |
+
batch_size: 1
|
| 26 |
+
|
| 27 |
+
logging:
|
| 28 |
+
metrics:
|
| 29 |
+
# - fvd
|
| 30 |
+
# - fid
|
| 31 |
+
# - lpips
|
configurations/huggingface.yaml
CHANGED
|
@@ -54,7 +54,7 @@ noise_level: random_all
|
|
| 54 |
causal: True
|
| 55 |
x_shape: [3, 360, 640]
|
| 56 |
context_frames: 1
|
| 57 |
-
diffusion_path: /
|
| 58 |
vae_path: yslan/worldmem_checkpoints/vae_only.ckpt
|
| 59 |
pose_predictor_path: yslan/worldmem_checkpoints/pose_prediction_model_only.ckpt
|
| 60 |
next_frame_length: 1
|
|
|
|
| 54 |
causal: True
|
| 55 |
x_shape: [3, 360, 640]
|
| 56 |
context_frames: 1
|
| 57 |
+
diffusion_path: yslan/worldmem_checkpoints/diffusion_only.ckpt
|
| 58 |
vae_path: yslan/worldmem_checkpoints/vae_only.ckpt
|
| 59 |
pose_predictor_path: yslan/worldmem_checkpoints/pose_prediction_model_only.ckpt
|
| 60 |
next_frame_length: 1
|
configurations/training.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# configuration parsing starts here
|
| 2 |
+
defaults:
|
| 3 |
+
- experiment: exp_video # experiment yaml file name in configurations/experiments folder [fixme]
|
| 4 |
+
- dataset: video_minecraft # dataset yaml file name in configurations/dataset folder [fixme]
|
| 5 |
+
- algorithm: df_video_worldmemminecraft # algorithm yaml file name in configurations/algorithm folder [fixme]
|
| 6 |
+
- cluster: null # optional, cluster yaml file name in configurations/cluster folder. Leave null for local compute
|
| 7 |
+
|
| 8 |
+
debug: false # global debug flag will be passed into configuration of experiment, dataset and algorithm
|
| 9 |
+
|
| 10 |
+
wandb:
|
| 11 |
+
entity: xizaoqu # wandb account name / organization name [fixme]
|
| 12 |
+
project: worldmem # wandb project name; if not provided, defaults to root folder name [fixme]
|
| 13 |
+
mode: online # set wandb logging to online, offline or dryrun
|
| 14 |
+
|
| 15 |
+
resume: null # wandb run id to resume logging and loading checkpoint from
|
| 16 |
+
load: null # wandb run id containing checkpoint or a path to a checkpoint file
|
datasets/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The `datasets` folder is used to contain dataset code or environment code.
|
| 2 |
+
Don't store actual data like images here! For those, please use the `data` folder instead of `datasets`.
|
| 3 |
+
|
| 4 |
+
Create a folder to create your own pytorch dataset definition. Then, update the `__init__.py`
|
| 5 |
+
at every level to register all datasets.
|
| 6 |
+
|
| 7 |
+
Each dataset class takes in a DictConfig file `cfg` in its `__init__`, which allows you to pass in arguments via configuration file in `configurations/dataset` or [command line override](https://hydra.cc/docs/tutorials/basic/your_first_app/simple_cli/).
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
This repo is forked from [Boyuan Chen](https://boyuan.space/)'s research template [repo](https://github.com/buoyancy99/research-template). By its MIT license, you must keep the above sentence in `README.md` and the `LICENSE` file to credit the author.
|
datasets/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .video import MinecraftVideoDataset
|
datasets/video/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .minecraft_video_dataset import MinecraftVideoDataset
|
datasets/video/base_video_dataset.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Sequence
|
| 2 |
+
import torch
|
| 3 |
+
import random
|
| 4 |
+
import os
|
| 5 |
+
import numpy as np
|
| 6 |
+
import cv2
|
| 7 |
+
from omegaconf import DictConfig
|
| 8 |
+
from torchvision import transforms
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from abc import abstractmethod, ABC
|
| 11 |
+
import json
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class BaseVideoDataset(torch.utils.data.Dataset, ABC):
|
| 15 |
+
"""
|
| 16 |
+
Base class for video datasets. Videos may be of variable length.
|
| 17 |
+
|
| 18 |
+
Folder structure of each dataset:
|
| 19 |
+
- [save_dir] (specified in config, e.g., data/phys101)
|
| 20 |
+
- /[split] (one per split)
|
| 21 |
+
- /data_folder_name (e.g., videos)
|
| 22 |
+
metadata.json
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, cfg: DictConfig, split: str = "training"):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.cfg = cfg
|
| 28 |
+
self.split = split
|
| 29 |
+
self.resolution = cfg.resolution
|
| 30 |
+
self.external_cond_dim = cfg.external_cond_dim
|
| 31 |
+
self.n_frames = (
|
| 32 |
+
cfg.n_frames * cfg.frame_skip
|
| 33 |
+
if split == "training"
|
| 34 |
+
else cfg.n_frames * cfg.frame_skip * cfg.validation_multiplier
|
| 35 |
+
)
|
| 36 |
+
self.frame_skip = cfg.frame_skip
|
| 37 |
+
self.save_dir = Path(cfg.save_dir)
|
| 38 |
+
self.save_dir.mkdir(exist_ok=True, parents=True)
|
| 39 |
+
self.split_dir = self.save_dir / f"{split}"
|
| 40 |
+
self.data_paths = self.get_data_paths(self.split)
|
| 41 |
+
|
| 42 |
+
if self.split == 'training':
|
| 43 |
+
self.metadata = [1300] * len(self.data_paths) # total 1500 f, randomly sampled ~1300 clips
|
| 44 |
+
else:
|
| 45 |
+
self.metadata = [1] * len(self.data_paths) # total 1500 f
|
| 46 |
+
|
| 47 |
+
self.clips_per_video = np.clip(np.array(self.metadata) - self.n_frames + 1, a_min=1, a_max=None).astype(
|
| 48 |
+
np.int32
|
| 49 |
+
)
|
| 50 |
+
self.cum_clips_per_video = np.cumsum(self.clips_per_video)
|
| 51 |
+
self.transform = transforms.Resize((self.resolution, self.resolution), antialias=True)
|
| 52 |
+
|
| 53 |
+
# shuffle but keep the same order for each epoch, so validation sample is diverse yet deterministic
|
| 54 |
+
random.seed(0)
|
| 55 |
+
self.idx_remap = list(range(self.__len__()))
|
| 56 |
+
random.shuffle(self.idx_remap)
|
| 57 |
+
|
| 58 |
+
@abstractmethod
|
| 59 |
+
def download_dataset(self) -> Sequence[int]:
|
| 60 |
+
"""
|
| 61 |
+
Download dataset from the internet and build it in save_dir
|
| 62 |
+
|
| 63 |
+
Returns a list of video lengths
|
| 64 |
+
"""
|
| 65 |
+
raise NotImplementedError
|
| 66 |
+
|
| 67 |
+
@abstractmethod
|
| 68 |
+
def get_data_paths(self, split):
|
| 69 |
+
"""Return a list of data paths (e.g. xxx.mp4) for a given split"""
|
| 70 |
+
raise NotImplementedError
|
| 71 |
+
|
| 72 |
+
def get_data_lengths(self, split):
|
| 73 |
+
"""Return a list of num_frames for each data path (e.g. xxx.mp4) for a given split"""
|
| 74 |
+
lengths = []
|
| 75 |
+
for path in self.get_data_paths(split):
|
| 76 |
+
length = cv2.VideoCapture(str(path)).get(cv2.CAP_PROP_FRAME_COUNT)
|
| 77 |
+
lengths.append(length)
|
| 78 |
+
return lengths
|
| 79 |
+
|
| 80 |
+
def split_idx(self, idx):
|
| 81 |
+
video_idx = np.argmax(self.cum_clips_per_video > idx)
|
| 82 |
+
frame_idx = idx - np.pad(self.cum_clips_per_video, (1, 0))[video_idx]
|
| 83 |
+
return video_idx, frame_idx
|
| 84 |
+
|
| 85 |
+
@staticmethod
|
| 86 |
+
def load_video(path: Path):
|
| 87 |
+
"""
|
| 88 |
+
Load video from a path
|
| 89 |
+
:param filename: path to the video
|
| 90 |
+
:return: video as a numpy array
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
cap = cv2.VideoCapture(str(path))
|
| 94 |
+
|
| 95 |
+
frames = []
|
| 96 |
+
while cap.isOpened():
|
| 97 |
+
ret, frame = cap.read()
|
| 98 |
+
if ret:
|
| 99 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 100 |
+
frames.append(frame)
|
| 101 |
+
else:
|
| 102 |
+
break
|
| 103 |
+
|
| 104 |
+
cap.release()
|
| 105 |
+
frames = np.stack(frames, dtype=np.uint8)
|
| 106 |
+
return np.transpose(frames, (0, 3, 1, 2)) # (T, C, H, W)
|
| 107 |
+
|
| 108 |
+
@staticmethod
|
| 109 |
+
def load_image(filename: Path):
|
| 110 |
+
"""
|
| 111 |
+
Load image from a path
|
| 112 |
+
:param filename: path to the image
|
| 113 |
+
:return: image as a numpy array
|
| 114 |
+
"""
|
| 115 |
+
image = cv2.imread(str(filename))
|
| 116 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 117 |
+
return np.transpose(image, (2, 0, 1))
|
| 118 |
+
|
| 119 |
+
def __len__(self):
|
| 120 |
+
return self.clips_per_video.sum()
|
| 121 |
+
|
| 122 |
+
def __getitem__(self, idx):
|
| 123 |
+
idx = self.idx_remap[idx]
|
| 124 |
+
video_idx, frame_idx = self.split_idx(idx)
|
| 125 |
+
video_path = self.data_paths[video_idx]
|
| 126 |
+
video = self.load_video(video_path)[frame_idx : frame_idx + self.n_frames]
|
| 127 |
+
|
| 128 |
+
pad_len = self.n_frames - len(video)
|
| 129 |
+
|
| 130 |
+
nonterminal = np.ones(self.n_frames)
|
| 131 |
+
if len(video) < self.n_frames:
|
| 132 |
+
video = np.pad(video, ((0, pad_len), (0, 0), (0, 0), (0, 0)))
|
| 133 |
+
nonterminal[-pad_len:] = 0
|
| 134 |
+
|
| 135 |
+
video = torch.from_numpy(video / 256.0).float()
|
| 136 |
+
video = self.transform(video)
|
| 137 |
+
|
| 138 |
+
if self.external_cond_dim:
|
| 139 |
+
external_cond = np.load(
|
| 140 |
+
# pylint: disable=no-member
|
| 141 |
+
self.condition_dir
|
| 142 |
+
/ f"{video_path.name.replace('.mp4', '.npy')}"
|
| 143 |
+
)
|
| 144 |
+
if len(external_cond) < self.n_frames:
|
| 145 |
+
external_cond = np.pad(external_cond, ((0, pad_len),))
|
| 146 |
+
external_cond = torch.from_numpy(external_cond).float()
|
| 147 |
+
return (
|
| 148 |
+
video[:: self.frame_skip],
|
| 149 |
+
external_cond[:: self.frame_skip],
|
| 150 |
+
nonterminal[:: self.frame_skip],
|
| 151 |
+
)
|
| 152 |
+
else:
|
| 153 |
+
return video[:: self.frame_skip], nonterminal[:: self.frame_skip]
|
datasets/video/minecraft_video_dataset.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import tarfile
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from typing import Sequence, Mapping
|
| 7 |
+
from omegaconf import DictConfig
|
| 8 |
+
from pytorchvideo.data.encoded_video import EncodedVideo
|
| 9 |
+
import random
|
| 10 |
+
|
| 11 |
+
from .base_video_dataset import BaseVideoDataset
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
ACTION_KEYS = [
|
| 17 |
+
"inventory",
|
| 18 |
+
"ESC",
|
| 19 |
+
"hotbar.1",
|
| 20 |
+
"hotbar.2",
|
| 21 |
+
"hotbar.3",
|
| 22 |
+
"hotbar.4",
|
| 23 |
+
"hotbar.5",
|
| 24 |
+
"hotbar.6",
|
| 25 |
+
"hotbar.7",
|
| 26 |
+
"hotbar.8",
|
| 27 |
+
"hotbar.9",
|
| 28 |
+
"forward",
|
| 29 |
+
"back",
|
| 30 |
+
"left",
|
| 31 |
+
"right",
|
| 32 |
+
"cameraY",
|
| 33 |
+
"cameraX",
|
| 34 |
+
"jump",
|
| 35 |
+
"sneak",
|
| 36 |
+
"sprint",
|
| 37 |
+
"swapHands",
|
| 38 |
+
"attack",
|
| 39 |
+
"use",
|
| 40 |
+
"pickItem",
|
| 41 |
+
"drop",
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
def convert_action_space(actions):
|
| 45 |
+
vec_25 = torch.zeros(len(actions), len(ACTION_KEYS))
|
| 46 |
+
vec_25[actions[:,0]==1, 11] = 1
|
| 47 |
+
vec_25[actions[:,0]==2, 12] = 1
|
| 48 |
+
vec_25[actions[:,4]==11, 16] = -1
|
| 49 |
+
vec_25[actions[:,4]==13, 16] = 1
|
| 50 |
+
vec_25[actions[:,3]==11, 15] = -1
|
| 51 |
+
vec_25[actions[:,3]==13, 15] = 1
|
| 52 |
+
vec_25[actions[:,5]==6, 24] = 1
|
| 53 |
+
vec_25[actions[:,5]==1, 24] = 1
|
| 54 |
+
vec_25[actions[:,1]==1, 13] = 1
|
| 55 |
+
vec_25[actions[:,1]==2, 14] = 1
|
| 56 |
+
vec_25[actions[:,7]==1, 2] = 1
|
| 57 |
+
return vec_25
|
| 58 |
+
|
| 59 |
+
# Dataset class
|
| 60 |
+
class MinecraftVideoDataset(BaseVideoDataset):
|
| 61 |
+
"""
|
| 62 |
+
Minecraft video dataset for training and validation.
|
| 63 |
+
|
| 64 |
+
Args:
|
| 65 |
+
cfg (DictConfig): Configuration object.
|
| 66 |
+
split (str): Dataset split ("training" or "validation").
|
| 67 |
+
"""
|
| 68 |
+
def __init__(self, cfg: DictConfig, split: str = "training"):
|
| 69 |
+
if split == "test":
|
| 70 |
+
split = "validation"
|
| 71 |
+
self.wo_updown = getattr(cfg, "wo_updown", False)
|
| 72 |
+
super().__init__(cfg, split)
|
| 73 |
+
self.n_frames = cfg.n_frames_valid if split == "validation" and hasattr(cfg, "n_frames_valid") else cfg.n_frames
|
| 74 |
+
self.memory_condition_length = cfg.memory_condition_length
|
| 75 |
+
self.customized_validation = cfg.customized_validation
|
| 76 |
+
if split == "training":
|
| 77 |
+
self.angle_range = cfg.angle_range
|
| 78 |
+
self.pos_range = cfg.pos_range
|
| 79 |
+
self.add_timestamp_embedding = cfg.add_timestamp_embedding
|
| 80 |
+
self.training_dropout = 0.1
|
| 81 |
+
self.memory_condition_length = getattr(cfg, "memory_condition_length", False)
|
| 82 |
+
self.sample_more_event = getattr(cfg, "sample_more_event", False)
|
| 83 |
+
self.causal_frame = getattr(cfg, "causal_frame", False)
|
| 84 |
+
|
| 85 |
+
def get_data_paths(self, split: str):
|
| 86 |
+
"""
|
| 87 |
+
Retrieve all video file paths for the given split.
|
| 88 |
+
|
| 89 |
+
Args:
|
| 90 |
+
split (str): Dataset split ("training" or "validation").
|
| 91 |
+
|
| 92 |
+
Returns:
|
| 93 |
+
List[Path]: List of video file paths.
|
| 94 |
+
"""
|
| 95 |
+
data_dir = self.save_dir / split
|
| 96 |
+
paths = sorted(list(data_dir.glob("**/*.mp4")), key=lambda x: x.name)
|
| 97 |
+
|
| 98 |
+
if self.wo_updown:
|
| 99 |
+
# Filter out paths containing "w_updown"
|
| 100 |
+
paths = [p for p in paths if "w_updown" not in str(p)]
|
| 101 |
+
|
| 102 |
+
if split == "validation" and self.wo_updown:
|
| 103 |
+
paths = [p for p in paths if "w_updown" not in str(p)]
|
| 104 |
+
elif split == "validation":
|
| 105 |
+
paths = [p for p in paths if "w_updown" in str(p)]
|
| 106 |
+
|
| 107 |
+
if not paths:
|
| 108 |
+
sub_dirs = os.listdir(data_dir)
|
| 109 |
+
for sub_dir in sub_dirs:
|
| 110 |
+
sub_path = data_dir / sub_dir
|
| 111 |
+
paths += sorted(list(sub_path.glob("**/*.mp4")), key=lambda x: x.name)
|
| 112 |
+
return paths
|
| 113 |
+
|
| 114 |
+
def download_dataset(self):
|
| 115 |
+
pass
|
| 116 |
+
|
| 117 |
+
def __getitem__(self, idx: int):
|
| 118 |
+
"""
|
| 119 |
+
Retrieve a single data sample by index.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
idx (int): Index of the data sample.
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
Tuple[torch.Tensor, torch.Tensor, np.ndarray, np.ndarray]: Video, actions, poses, and timestamps.
|
| 126 |
+
"""
|
| 127 |
+
max_retries = 1000
|
| 128 |
+
for _ in range(max_retries):
|
| 129 |
+
try:
|
| 130 |
+
return self.load_data(idx)
|
| 131 |
+
except Exception as e:
|
| 132 |
+
print(f"Retrying due to error: {e}")
|
| 133 |
+
idx = (idx + 1) % len(self)
|
| 134 |
+
|
| 135 |
+
def load_data(self, idx):
|
| 136 |
+
# === 1. Remap index and skip first few frames ===
|
| 137 |
+
idx = self.idx_remap[idx]
|
| 138 |
+
file_idx, frame_idx = self.split_idx(idx)
|
| 139 |
+
frame_idx += 100 # initial few frames are low quality
|
| 140 |
+
|
| 141 |
+
# === 2. Load paths and data arrays ===
|
| 142 |
+
video_path = self.data_paths[file_idx]
|
| 143 |
+
action_path = video_path.with_suffix(".npz")
|
| 144 |
+
data = np.load(action_path)
|
| 145 |
+
actions_pool = convert_action_space(data["actions"])
|
| 146 |
+
poses_pool = data["poses"]
|
| 147 |
+
|
| 148 |
+
# Fix corrupted height (maybe) in the first frame
|
| 149 |
+
poses_pool[0, 1] = poses_pool[1, 1]
|
| 150 |
+
assert poses_pool[:, 1].ptp() < 2, f"Height variation too large: {poses_pool[:, 1].ptp()} - {video_path}"
|
| 151 |
+
|
| 152 |
+
# Pad poses if shorter than actions
|
| 153 |
+
if len(poses_pool) < len(actions_pool):
|
| 154 |
+
poses_pool = np.pad(poses_pool, ((1, 0), (0, 0)))
|
| 155 |
+
|
| 156 |
+
# === 3. Load video clip ===
|
| 157 |
+
video_raw = EncodedVideo.from_path(video_path, decode_audio=False)
|
| 158 |
+
fps = 10
|
| 159 |
+
clip = video_raw.get_clip(
|
| 160 |
+
start_sec=frame_idx / fps,
|
| 161 |
+
end_sec=(frame_idx + self.n_frames) / fps
|
| 162 |
+
)["video"]
|
| 163 |
+
video = clip.permute(1, 2, 3, 0).numpy()
|
| 164 |
+
|
| 165 |
+
actions = np.copy(actions_pool[frame_idx : frame_idx + self.n_frames])
|
| 166 |
+
poses = np.copy(poses_pool[frame_idx : frame_idx + self.n_frames])
|
| 167 |
+
|
| 168 |
+
# === 4. Normalize poses relative to current segment ===
|
| 169 |
+
def normalize_pose(pose, ref_pose):
|
| 170 |
+
pose[:, :3] -= ref_pose[:1, :3]
|
| 171 |
+
pose[:, -1] = -pose[:, -1]
|
| 172 |
+
pose[:, 3:] %= 360
|
| 173 |
+
return pose
|
| 174 |
+
|
| 175 |
+
poses_pool = normalize_pose(poses_pool, poses)
|
| 176 |
+
poses = normalize_pose(poses, poses)
|
| 177 |
+
|
| 178 |
+
assert len(video) >= self.n_frames, f"{video_path}"
|
| 179 |
+
|
| 180 |
+
# === 5. Sample memory frames for training ===
|
| 181 |
+
if self.split == "training" and self.memory_condition_length > 0:
|
| 182 |
+
use_memory = random.uniform(0, 1) > self.training_dropout
|
| 183 |
+
|
| 184 |
+
if use_memory:
|
| 185 |
+
# Compute pose distance between current and candidate frames
|
| 186 |
+
dis = np.abs(poses[:, None] - poses_pool[None, :])
|
| 187 |
+
dis[..., 3:][dis[..., 3:] > 180] = 360 - dis[..., 3:][dis[..., 3:] > 180]
|
| 188 |
+
|
| 189 |
+
spatial_match = (dis[..., :3] <= self.pos_range).sum(-1) >= 3
|
| 190 |
+
angular_match = (dis[..., 3:] <= self.angle_range).sum(-1) >= 2
|
| 191 |
+
not_exact_match = ((dis[..., :3] > 0).sum(-1) >= 1) | ((dis[..., 3:] > 0).sum(-1) >= 1)
|
| 192 |
+
|
| 193 |
+
valid_index = (spatial_match & angular_match & not_exact_match).sum(0)
|
| 194 |
+
valid_index[:100] = 0 # skip unstable early frames
|
| 195 |
+
|
| 196 |
+
# Exclude future if causality and timestamp are enabled
|
| 197 |
+
if self.add_timestamp_embedding and self.causal_frame and (actions_pool[:frame_idx, 24] == 1).sum() > 0:
|
| 198 |
+
valid_index[frame_idx:] = 0
|
| 199 |
+
|
| 200 |
+
# Select indices satisfying condition
|
| 201 |
+
mask = valid_index >= 1
|
| 202 |
+
mask[0] = False
|
| 203 |
+
candidate_indices = np.argwhere(mask)
|
| 204 |
+
|
| 205 |
+
# Backup candidates with weaker conditions
|
| 206 |
+
mask2 = valid_index >= 0
|
| 207 |
+
mask2[0] = False
|
| 208 |
+
|
| 209 |
+
count = min(self.memory_condition_length, candidate_indices.shape[0])
|
| 210 |
+
selected = candidate_indices[np.random.choice(candidate_indices.shape[0], count, replace=True)][:, 0]
|
| 211 |
+
|
| 212 |
+
if count < self.memory_condition_length:
|
| 213 |
+
extra = np.argwhere(mask2)
|
| 214 |
+
extra = extra[np.random.choice(extra.shape[0], self.memory_condition_length - count, replace=True)][:, 0]
|
| 215 |
+
selected = np.concatenate([selected, extra])
|
| 216 |
+
|
| 217 |
+
# Prioritize event-trigger frames if applicable
|
| 218 |
+
if self.sample_more_event and random.uniform(0, 1) > 0.3:
|
| 219 |
+
event_idx = torch.nonzero(actions_pool[:frame_idx, 24] == 1)[:, 0]
|
| 220 |
+
if len(event_idx) > self.memory_condition_length // 2:
|
| 221 |
+
event_idx = event_idx[-self.memory_condition_length // 2:]
|
| 222 |
+
if len(event_idx) > 0:
|
| 223 |
+
selected[-len(event_idx):] = event_idx + 4
|
| 224 |
+
|
| 225 |
+
else:
|
| 226 |
+
selected = np.full(self.memory_condition_length, random.randint(0, frame_idx))
|
| 227 |
+
|
| 228 |
+
# === 6. Retrieve video frames for selected memory indices ===
|
| 229 |
+
video_pool = []
|
| 230 |
+
for si in selected:
|
| 231 |
+
frame = video_raw.get_clip(start_sec=si / fps, end_sec=(si + 1) / fps)["video"][:, 0].permute(1, 2, 0)
|
| 232 |
+
video_pool.append(frame)
|
| 233 |
+
|
| 234 |
+
video = np.concatenate([video, np.stack(video_pool)], axis=0)
|
| 235 |
+
actions = np.concatenate([actions, actions_pool[selected]], axis=0)
|
| 236 |
+
poses = np.concatenate([poses, poses_pool[selected]], axis=0)
|
| 237 |
+
timestamp = np.concatenate([np.arange(frame_idx, frame_idx + self.n_frames), selected])
|
| 238 |
+
else:
|
| 239 |
+
timestamp = np.arange(self.n_frames)
|
| 240 |
+
|
| 241 |
+
# === 7. Convert video to torch format ===
|
| 242 |
+
video = torch.from_numpy(video / 255.0).float().permute(0, 3, 1, 2).contiguous()
|
| 243 |
+
|
| 244 |
+
# === 9. Return all items ===
|
| 245 |
+
return (
|
| 246 |
+
video[:: self.frame_skip],
|
| 247 |
+
actions[:: self.frame_skip],
|
| 248 |
+
poses[:: self.frame_skip],
|
| 249 |
+
timestamp
|
| 250 |
+
)
|
infer.sh
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
wandb enabled
|
| 2 |
+
python -m main +name=infer \
|
| 3 |
+
experiment.tasks=[validation] \
|
| 4 |
+
dataset.validation_multiplier=1 \
|
| 5 |
+
+diffusion_model_path=yslan/worldmem_checkpoints/diffusion_only.ckpt \
|
| 6 |
+
+vae_path=yslan/worldmem_checkpoints/vae_only.ckpt \
|
| 7 |
+
+load=/mnt/xiaozeqi/checkpoints/2025-03-31-22-24-06-epoch1step240000_2d_largerange.ckpt \
|
| 8 |
+
+customized_load=true \
|
| 9 |
+
+seperate_load=false \
|
| 10 |
+
dataset.n_frames=8 \
|
| 11 |
+
dataset.save_dir=data/minecraft \
|
| 12 |
+
+dataset.n_frames_valid=700 \
|
| 13 |
+
+dataset.memory_condition_length=8 \
|
| 14 |
+
+dataset.customized_validation=true \
|
| 15 |
+
+dataset.add_timestamp_embedding=true \
|
| 16 |
+
+algorithm.n_tokens=8 \
|
| 17 |
+
+algorithm.memory_condition_length=8 \
|
| 18 |
+
algorithm.context_frames=600 \
|
| 19 |
+
+algorithm.relative_embedding=true \
|
| 20 |
+
+algorithm.log_video=true \
|
| 21 |
+
+algorithm.add_timestamp_embedding=true \
|
| 22 |
+
algorithm.metrics=[lpips,psnr] \
|
train_stage_1.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
wandb enabled
|
| 2 |
+
|
| 3 |
+
# set -e
|
| 4 |
+
python -m main +name=train \
|
| 5 |
+
+diffusion_model_path=your_diffusion_model_path \
|
| 6 |
+
+vae_path=your_vae_path \
|
| 7 |
+
+customized_load=true \
|
| 8 |
+
+seperate_load=true \
|
| 9 |
+
+zero_init_gate=true \
|
| 10 |
+
dataset.n_frames=8 \
|
| 11 |
+
dataset.save_dir=data/minecraft \
|
| 12 |
+
+dataset.n_frames_valid=700 \
|
| 13 |
+
+dataset.angle_range=110 \
|
| 14 |
+
+dataset.pos_range=2 \
|
| 15 |
+
+dataset.memory_condition_length=8 \
|
| 16 |
+
+dataset.customized_validation=true \
|
| 17 |
+
+dataset.add_timestamp_embedding=true \
|
| 18 |
+
+dataset.wo_updown=true \
|
| 19 |
+
+algorithm.n_tokens=8 \
|
| 20 |
+
+algorithm.memory_condition_length=8 \
|
| 21 |
+
algorithm.context_frames=600 \
|
| 22 |
+
+algorithm.relative_embedding=true \
|
| 23 |
+
+algorithm.log_video=true \
|
| 24 |
+
+algorithm.add_timestamp_embedding=true \
|
| 25 |
+
algorithm.metrics=[lpips,psnr] \
|
| 26 |
+
experiment.training.checkpointing.every_n_train_steps=2500 \
|
| 27 |
+
experiment.training.max_steps=120000
|
| 28 |
+
|
| 29 |
+
|
train_stage_2.sh
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
wandb enabled
|
| 2 |
+
|
| 3 |
+
# set -e
|
| 4 |
+
python -m main +name=train \
|
| 5 |
+
dataset.n_frames=8 \
|
| 6 |
+
dataset.save_dir=data/minecraft \
|
| 7 |
+
+dataset.n_frames_valid=700 \
|
| 8 |
+
+dataset.angle_range=110 \
|
| 9 |
+
+dataset.pos_range=8 \
|
| 10 |
+
+dataset.memory_condition_length=8 \
|
| 11 |
+
+dataset.customized_validation=true \
|
| 12 |
+
+dataset.add_timestamp_embedding=true \
|
| 13 |
+
+dataset.wo_updown=true \
|
| 14 |
+
+algorithm.n_tokens=8 \
|
| 15 |
+
+algorithm.memory_condition_length=8 \
|
| 16 |
+
algorithm.context_frames=600 \
|
| 17 |
+
+algorithm.relative_embedding=true \
|
| 18 |
+
+algorithm.log_video=true \
|
| 19 |
+
+algorithm.add_timestamp_embedding=true \
|
| 20 |
+
algorithm.metrics=[lpips,psnr] \
|
| 21 |
+
experiment.training.checkpointing.every_n_train_steps=2500 \
|
| 22 |
+
resume=your_wandb_job_id e.g.yhht29bz \
|
| 23 |
+
+output_dir=your_saving_path e.g. outputs/2025-05-18/15-16-32 \
|
| 24 |
+
experiment.training.max_steps=240000
|
| 25 |
+
|
train_stage_3.sh
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
wandb enabled
|
| 2 |
+
|
| 3 |
+
# set -e
|
| 4 |
+
python -m main +name=train \
|
| 5 |
+
dataset.n_frames=8 \
|
| 6 |
+
dataset.save_dir=data/minecraft \
|
| 7 |
+
+dataset.n_frames_valid=700 \
|
| 8 |
+
+dataset.angle_range=110 \
|
| 9 |
+
+dataset.pos_range=8 \
|
| 10 |
+
+dataset.memory_condition_length=8 \
|
| 11 |
+
+dataset.customized_validation=true \
|
| 12 |
+
+dataset.add_timestamp_embedding=true \
|
| 13 |
+
+dataset.wo_updown=false \
|
| 14 |
+
+algorithm.n_tokens=8 \
|
| 15 |
+
+algorithm.memory_condition_length=8 \
|
| 16 |
+
algorithm.context_frames=600 \
|
| 17 |
+
+algorithm.relative_embedding=true \
|
| 18 |
+
+algorithm.log_video=true \
|
| 19 |
+
+algorithm.add_timestamp_embedding=true \
|
| 20 |
+
algorithm.metrics=[lpips,psnr] \
|
| 21 |
+
experiment.training.checkpointing.every_n_train_steps=2500 \
|
| 22 |
+
resume=your_wandb_job_id e.g.yhht29bz \
|
| 23 |
+
+output_dir=your_saving_path e.g. outputs/2025-05-18/15-16-32 \
|
| 24 |
+
experiment.training.max_steps=700000
|