cahlen commited on
Commit
8c3bc34
·
1 Parent(s): 6d13b71

Add pre-quantized NF4 weights and complete inference package

Browse files

- NF4 quantized diffusion models (high_noise + low_noise) via bitsandbytes
- T5-XXL text encoder (models_t5_umt5-xxl-enc-bf16.pth)
- VAE encoder/decoder (Wan2.1_VAE.pth)
- Tokenizer files from google/umt5-xxl
- Complete wan/ module for inference
- generate_prequant.py - main inference script
- load_prequant.py - pre-quantized weight loader

Total package: ~30GB (vs ~85GB for full precision)
Fits in 32GB VRAM with model swapping between GPU/CPU

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -1
  2. README.md +113 -0
  3. Wan2.1_VAE.pth +3 -0
  4. generate_prequant.py +425 -0
  5. high_noise_model_bnb_nf4/config.json +15 -0
  6. high_noise_model_bnb_nf4/model.safetensors +3 -0
  7. high_noise_model_bnb_nf4/quantization_meta.json +54 -0
  8. load_prequant.py +412 -0
  9. low_noise_model_bnb_nf4/config.json +15 -0
  10. low_noise_model_bnb_nf4/model.safetensors +3 -0
  11. low_noise_model_bnb_nf4/quantization_meta.json +54 -0
  12. models_t5_umt5-xxl-enc-bf16.pth +3 -0
  13. requirements.txt +15 -0
  14. tokenizer/special_tokens_map.json +3 -0
  15. tokenizer/tokenizer.json +3 -0
  16. tokenizer/tokenizer_config.json +3 -0
  17. wan/__init__.py +2 -0
  18. wan/__pycache__/__init__.cpython-310.pyc +0 -0
  19. wan/__pycache__/image2video.cpython-310.pyc +0 -0
  20. wan/configs/__init__.py +36 -0
  21. wan/configs/__pycache__/__init__.cpython-310.pyc +0 -0
  22. wan/configs/__pycache__/shared_config.cpython-310.pyc +0 -0
  23. wan/configs/__pycache__/wan_i2v_A14B.cpython-310.pyc +0 -0
  24. wan/configs/shared_config.py +19 -0
  25. wan/configs/wan_i2v_A14B.py +37 -0
  26. wan/distributed/__init__.py +0 -0
  27. wan/distributed/__pycache__/__init__.cpython-310.pyc +0 -0
  28. wan/distributed/__pycache__/fsdp.cpython-310.pyc +0 -0
  29. wan/distributed/__pycache__/sequence_parallel.cpython-310.pyc +0 -0
  30. wan/distributed/__pycache__/ulysses.cpython-310.pyc +0 -0
  31. wan/distributed/__pycache__/util.cpython-310.pyc +0 -0
  32. wan/distributed/fsdp.py +44 -0
  33. wan/distributed/sequence_parallel.py +213 -0
  34. wan/distributed/ulysses.py +46 -0
  35. wan/distributed/util.py +50 -0
  36. wan/image2video.py +489 -0
  37. wan/modules/__init__.py +18 -0
  38. wan/modules/__pycache__/__init__.cpython-310.pyc +0 -0
  39. wan/modules/__pycache__/attention.cpython-310.pyc +0 -0
  40. wan/modules/__pycache__/model.cpython-310.pyc +0 -0
  41. wan/modules/__pycache__/t5.cpython-310.pyc +0 -0
  42. wan/modules/__pycache__/tokenizers.cpython-310.pyc +0 -0
  43. wan/modules/__pycache__/vae2_1.cpython-310.pyc +0 -0
  44. wan/modules/__pycache__/vae2_2.cpython-310.pyc +0 -0
  45. wan/modules/animate/__init__.py +4 -0
  46. wan/modules/animate/animate_utils.py +143 -0
  47. wan/modules/animate/clip.py +542 -0
  48. wan/modules/animate/face_blocks.py +383 -0
  49. wan/modules/animate/model_animate.py +500 -0
  50. wan/modules/animate/motion_encoder.py +307 -0
.gitattributes CHANGED
@@ -3,9 +3,9 @@
3
  *.pth filter=lfs diff=lfs merge=lfs -text
4
  *.pt filter=lfs diff=lfs merge=lfs -text
5
  *.bin filter=lfs diff=lfs merge=lfs -text
6
-
7
  # Keep these as text
8
  *.json text
9
  *.md text
10
  *.py text
11
  *.txt text
 
 
3
  *.pth filter=lfs diff=lfs merge=lfs -text
4
  *.pt filter=lfs diff=lfs merge=lfs -text
5
  *.bin filter=lfs diff=lfs merge=lfs -text
 
6
  # Keep these as text
7
  *.json text
8
  *.md text
9
  *.py text
10
  *.txt text
11
+ tokenizer/*.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LingBot-World NF4 Quantized
2
+
3
+ Pre-quantized NF4 weights for LingBot-World video generation model. This is a complete, self-contained package - no additional downloads required.
4
+
5
+ ## Features
6
+
7
+ - **4-bit NF4 quantization** via bitsandbytes - fits in 32GB VRAM
8
+ - **Pre-quantized weights** - no runtime quantization overhead
9
+ - **Complete package** - includes T5 encoder, VAE, and diffusion models
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Clone the repo
15
+ git clone https://huggingface.co/cahlen/lingbot-world-base-cam-nf4
16
+ cd lingbot-world-base-cam-nf4
17
+
18
+ # Install dependencies
19
+ pip install -r requirements.txt
20
+
21
+ # Generate a video
22
+ python generate_prequant.py \
23
+ --image your_image.jpg \
24
+ --prompt "A cinematic video of the scene" \
25
+ --frame_num 81 \
26
+ --output output.mp4
27
+ ```
28
+
29
+ ## Model Contents
30
+
31
+ | File | Size | Description |
32
+ |------|------|-------------|
33
+ | `high_noise_model_bnb_nf4/model.safetensors` | ~9.6GB | NF4 quantized diffusion model (high noise) |
34
+ | `low_noise_model_bnb_nf4/model.safetensors` | ~9.6GB | NF4 quantized diffusion model (low noise) |
35
+ | `models_t5_umt5-xxl-enc-bf16.pth` | ~10.6GB | T5-XXL text encoder |
36
+ | `Wan2.1_VAE.pth` | ~485MB | VAE encoder/decoder |
37
+
38
+ **Total size: ~30GB** (vs ~85GB for full precision models)
39
+
40
+ ## Usage
41
+
42
+ ### Basic Generation
43
+
44
+ ```bash
45
+ python generate_prequant.py \
46
+ --image input.jpg \
47
+ --prompt "Your prompt here" \
48
+ --frame_num 81 \
49
+ --size "480*832" \
50
+ --output output.mp4
51
+ ```
52
+
53
+ ### Parameters
54
+
55
+ | Parameter | Default | Description |
56
+ |-----------|---------|-------------|
57
+ | `--image` | required | Input image path |
58
+ | `--prompt` | required | Text prompt describing the video |
59
+ | `--frame_num` | 81 | Number of frames (81 = ~5 seconds at 16fps) |
60
+ | `--size` | "480*832" | Output resolution (height*width) |
61
+ | `--sampling_steps` | 40 | Diffusion sampling steps |
62
+ | `--guide_scale` | 5.0 | Classifier-free guidance scale |
63
+ | `--seed` | -1 | Random seed (-1 for random) |
64
+ | `--output` | "output.mp4" | Output video path |
65
+
66
+ ### With Camera Control
67
+
68
+ ```bash
69
+ python generate_prequant.py \
70
+ --image input.jpg \
71
+ --prompt "Your prompt" \
72
+ --action_path /path/to/camera_poses/ \
73
+ --frame_num 81
74
+ ```
75
+
76
+ Camera pose directory should contain:
77
+ - `poses.npy`: Shape `[num_frames, 4, 4]` - camera transformation matrices
78
+ - `intrinsics.npy`: Shape `[num_frames, 4]` - `[fx, fy, cx, cy]`
79
+
80
+ ## Requirements
81
+
82
+ - Python 3.10+
83
+ - CUDA 11.8+ (tested with CUDA 12.x)
84
+ - ~32GB VRAM (RTX 4090, RTX 5090, A100, etc.)
85
+
86
+ ## Quantization Details
87
+
88
+ The diffusion models are quantized using bitsandbytes NF4 with double quantization:
89
+
90
+ ```json
91
+ {
92
+ "format": "bnb_nf4",
93
+ "double_quant": true,
94
+ "compute_dtype": "bfloat16",
95
+ "blocksize": 64
96
+ }
97
+ ```
98
+
99
+ This achieves ~3.9x compression while maintaining generation quality.
100
+
101
+ ## License
102
+
103
+ This model is based on [LingBot-World](https://github.com/robbyant/lingbot-world) and follows its license terms.
104
+
105
+ ## Citation
106
+
107
+ ```bibtex
108
+ @misc{lingbot-world-nf4,
109
+ title={LingBot-World NF4 Quantized},
110
+ year={2025},
111
+ url={https://huggingface.co/cahlen/lingbot-world-base-cam-nf4}
112
+ }
113
+ ```
Wan2.1_VAE.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38071ab59bd94681c686fa51d75a1968f64e470262043be31f7a094e442fd981
3
+ size 507609880
generate_prequant.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate videos using PRE-QUANTIZED bitsandbytes NF4 models.
4
+
5
+ Unlike generate_bnb.py which re-quantizes at runtime, this script loads
6
+ pre-quantized weights directly. No base model weights are needed.
7
+
8
+ Prerequisites:
9
+ - Pre-quantized models in {ckpt_dir}/{high,low}_noise_model_bnb_nf4/
10
+ - Each should contain model.safetensors (or model.pt) + config.json
11
+
12
+ Usage:
13
+ python generate_prequant.py \
14
+ --image examples/00/image.jpg \
15
+ --prompt "A cinematic video of the scene" \
16
+ --frame_num 81 \
17
+ --size 480*832
18
+ """
19
+
20
+ import argparse
21
+ import gc
22
+ import logging
23
+ import os
24
+ import random
25
+ import sys
26
+ from pathlib import Path
27
+
28
+ import numpy as np
29
+ import torch
30
+ import torchvision.transforms.functional as TF
31
+ from PIL import Image
32
+ from tqdm import tqdm
33
+
34
+ sys.path.insert(0, str(Path(__file__).parent))
35
+
36
+ from einops import rearrange
37
+
38
+ from load_prequant import load_quantized_model
39
+ from wan.configs.wan_i2v_A14B import i2v_A14B as cfg
40
+ from wan.modules.t5 import T5EncoderModel
41
+ from wan.modules.vae2_1 import Wan2_1_VAE
42
+ from wan.utils.cam_utils import (
43
+ compute_relative_poses,
44
+ get_Ks_transformed,
45
+ get_plucker_embeddings,
46
+ interpolate_camera_poses,
47
+ )
48
+ from wan.utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
49
+
50
+ logging.basicConfig(level=logging.INFO)
51
+ logger = logging.getLogger(__name__)
52
+
53
+
54
+ class WanI2V_PreQuant:
55
+ """Image-to-video pipeline using pre-quantized NF4 models."""
56
+
57
+ def __init__(
58
+ self,
59
+ checkpoint_dir: str,
60
+ device_id: int = 0,
61
+ t5_cpu: bool = True,
62
+ ):
63
+ self.device = torch.device(f"cuda:{device_id}")
64
+ self.config = cfg
65
+ self.t5_cpu = t5_cpu
66
+
67
+ self.num_train_timesteps = cfg.num_train_timesteps
68
+ self.boundary = cfg.boundary
69
+ self.param_dtype = cfg.param_dtype
70
+ self.vae_stride = cfg.vae_stride
71
+ self.patch_size = cfg.patch_size
72
+ self.sample_neg_prompt = cfg.sample_neg_prompt
73
+
74
+ # Load T5 encoder (not quantized)
75
+ logger.info("Loading T5 encoder...")
76
+ # Use local tokenizer if available, otherwise fall back to HuggingFace
77
+ local_tokenizer = os.path.join(checkpoint_dir, "tokenizer")
78
+ tokenizer_path = local_tokenizer if os.path.isdir(local_tokenizer) else cfg.t5_tokenizer
79
+ self.text_encoder = T5EncoderModel(
80
+ text_len=cfg.text_len,
81
+ dtype=cfg.t5_dtype,
82
+ device=torch.device("cpu"),
83
+ checkpoint_path=os.path.join(checkpoint_dir, cfg.t5_checkpoint),
84
+ tokenizer_path=tokenizer_path,
85
+ shard_fn=None,
86
+ )
87
+
88
+ # Load VAE (not quantized)
89
+ logger.info("Loading VAE...")
90
+ self.vae = Wan2_1_VAE(
91
+ vae_pth=os.path.join(checkpoint_dir, cfg.vae_checkpoint),
92
+ device=self.device,
93
+ )
94
+
95
+ # Load PRE-QUANTIZED diffusion models
96
+ logger.info("Loading pre-quantized NF4 diffusion models...")
97
+
98
+ low_noise_dir = os.path.join(
99
+ checkpoint_dir, cfg.low_noise_checkpoint + "_bnb_nf4"
100
+ )
101
+ high_noise_dir = os.path.join(
102
+ checkpoint_dir, cfg.high_noise_checkpoint + "_bnb_nf4"
103
+ )
104
+
105
+ # Verify directories exist
106
+ for d in [low_noise_dir, high_noise_dir]:
107
+ if not os.path.isdir(d):
108
+ raise FileNotFoundError(
109
+ f"Pre-quantized model not found: {d}\n"
110
+ "Run: python scripts/quantize_and_package.py first"
111
+ )
112
+
113
+ # Load to CPU first, we'll swap to GPU as needed
114
+ self.low_noise_model = load_quantized_model(low_noise_dir, device="cpu")
115
+ self.high_noise_model = load_quantized_model(high_noise_dir, device="cpu")
116
+
117
+ logger.info("Model loading complete!")
118
+
119
+ def _prepare_model_for_timestep(self, t, boundary):
120
+ """Prepare and return the required model for the current timestep."""
121
+ if t.item() >= boundary:
122
+ required_model_name = "high_noise_model"
123
+ offload_model_name = "low_noise_model"
124
+ else:
125
+ required_model_name = "low_noise_model"
126
+ offload_model_name = "high_noise_model"
127
+
128
+ required_model = getattr(self, required_model_name)
129
+ offload_model = getattr(self, offload_model_name)
130
+
131
+ # Offload unused model to CPU
132
+ try:
133
+ if next(offload_model.parameters()).device.type == "cuda":
134
+ offload_model.to("cpu")
135
+ torch.cuda.empty_cache()
136
+ except StopIteration:
137
+ pass
138
+
139
+ # Load required model to GPU
140
+ try:
141
+ if next(required_model.parameters()).device.type == "cpu":
142
+ required_model.to(self.device)
143
+ except StopIteration:
144
+ pass
145
+
146
+ return required_model
147
+
148
+ def generate(
149
+ self,
150
+ input_prompt: str,
151
+ img: Image.Image,
152
+ action_path: str = None,
153
+ max_area: int = 720 * 1280,
154
+ frame_num: int = 81,
155
+ shift: float = 5.0,
156
+ sampling_steps: int = 40,
157
+ guide_scale: float = 5.0,
158
+ n_prompt: str = "",
159
+ seed: int = -1,
160
+ ):
161
+ """Generate video from image and text prompt."""
162
+ if action_path is not None:
163
+ c2ws = np.load(os.path.join(action_path, "poses.npy"))
164
+ len_c2ws = ((len(c2ws) - 1) // 4) * 4 + 1
165
+ frame_num = min(frame_num, len_c2ws)
166
+ c2ws = c2ws[:frame_num]
167
+
168
+ guide_scale = (
169
+ (guide_scale, guide_scale)
170
+ if isinstance(guide_scale, float)
171
+ else guide_scale
172
+ )
173
+ img_tensor = TF.to_tensor(img).sub_(0.5).div_(0.5).to(self.device)
174
+
175
+ F = frame_num
176
+ h, w = img_tensor.shape[1:]
177
+ aspect_ratio = h / w
178
+ lat_h = round(
179
+ np.sqrt(max_area * aspect_ratio)
180
+ // self.vae_stride[1]
181
+ // self.patch_size[1]
182
+ * self.patch_size[1]
183
+ )
184
+ lat_w = round(
185
+ np.sqrt(max_area / aspect_ratio)
186
+ // self.vae_stride[2]
187
+ // self.patch_size[2]
188
+ * self.patch_size[2]
189
+ )
190
+ h = lat_h * self.vae_stride[1]
191
+ w = lat_w * self.vae_stride[2]
192
+ lat_f = (F - 1) // self.vae_stride[0] + 1
193
+ max_seq_len = (
194
+ lat_f * lat_h * lat_w // (self.patch_size[1] * self.patch_size[2])
195
+ )
196
+
197
+ seed = seed if seed >= 0 else random.randint(0, sys.maxsize)
198
+ seed_g = torch.Generator(device=self.device)
199
+ seed_g.manual_seed(seed)
200
+ noise = torch.randn(
201
+ 16,
202
+ (F - 1) // self.vae_stride[0] + 1,
203
+ lat_h,
204
+ lat_w,
205
+ dtype=torch.float32,
206
+ generator=seed_g,
207
+ device=self.device,
208
+ )
209
+
210
+ msk = torch.ones(1, F, lat_h, lat_w, device=self.device)
211
+ msk[:, 1:] = 0
212
+ msk = torch.concat(
213
+ [torch.repeat_interleave(msk[:, 0:1], repeats=4, dim=1), msk[:, 1:]], dim=1
214
+ )
215
+ msk = msk.view(1, msk.shape[1] // 4, 4, lat_h, lat_w)
216
+ msk = msk.transpose(1, 2)[0]
217
+
218
+ if n_prompt == "":
219
+ n_prompt = self.sample_neg_prompt
220
+
221
+ # Encode text
222
+ if not self.t5_cpu:
223
+ self.text_encoder.model.to(self.device)
224
+ context = self.text_encoder([input_prompt], self.device)
225
+ context_null = self.text_encoder([n_prompt], self.device)
226
+ self.text_encoder.model.cpu()
227
+ else:
228
+ context = self.text_encoder([input_prompt], torch.device("cpu"))
229
+ context_null = self.text_encoder([n_prompt], torch.device("cpu"))
230
+ context = [t.to(self.device) for t in context]
231
+ context_null = [t.to(self.device) for t in context_null]
232
+
233
+ # Camera preparation
234
+ dit_cond_dict = None
235
+ if action_path is not None:
236
+ Ks = torch.from_numpy(
237
+ np.load(os.path.join(action_path, "intrinsics.npy"))
238
+ ).float()
239
+ Ks = get_Ks_transformed(Ks, 480, 832, h, w, h, w)
240
+ Ks = Ks[0]
241
+
242
+ len_c2ws = len(c2ws)
243
+ c2ws_infer = interpolate_camera_poses(
244
+ src_indices=np.linspace(0, len_c2ws - 1, len_c2ws),
245
+ src_rot_mat=c2ws[:, :3, :3],
246
+ src_trans_vec=c2ws[:, :3, 3],
247
+ tgt_indices=np.linspace(
248
+ 0, len_c2ws - 1, int((len_c2ws - 1) // 4) + 1
249
+ ),
250
+ )
251
+ c2ws_infer = compute_relative_poses(c2ws_infer, framewise=True)
252
+ Ks = Ks.repeat(len(c2ws_infer), 1)
253
+
254
+ c2ws_infer = c2ws_infer.to(self.device)
255
+ Ks = Ks.to(self.device)
256
+ c2ws_plucker_emb = get_plucker_embeddings(c2ws_infer, Ks, h, w)
257
+ c2ws_plucker_emb = rearrange(
258
+ c2ws_plucker_emb,
259
+ "f (h c1) (w c2) c -> (f h w) (c c1 c2)",
260
+ c1=int(h // lat_h),
261
+ c2=int(w // lat_w),
262
+ )
263
+ c2ws_plucker_emb = c2ws_plucker_emb[None, ...]
264
+ c2ws_plucker_emb = rearrange(
265
+ c2ws_plucker_emb,
266
+ "b (f h w) c -> b c f h w",
267
+ f=lat_f,
268
+ h=lat_h,
269
+ w=lat_w,
270
+ ).to(self.param_dtype)
271
+ dit_cond_dict = {"c2ws_plucker_emb": c2ws_plucker_emb.chunk(1, dim=0)}
272
+
273
+ # Encode image
274
+ y = self.vae.encode(
275
+ [
276
+ torch.concat(
277
+ [
278
+ torch.nn.functional.interpolate(
279
+ img_tensor[None].cpu(), size=(h, w), mode="bicubic"
280
+ ).transpose(0, 1),
281
+ torch.zeros(3, F - 1, h, w),
282
+ ],
283
+ dim=1,
284
+ ).to(self.device)
285
+ ]
286
+ )[0]
287
+ y = torch.concat([msk, y])
288
+
289
+ # Diffusion sampling
290
+ with torch.amp.autocast("cuda", dtype=self.param_dtype), torch.no_grad():
291
+ boundary = self.boundary * self.num_train_timesteps
292
+
293
+ sample_scheduler = FlowUniPCMultistepScheduler(
294
+ num_train_timesteps=self.num_train_timesteps,
295
+ shift=1,
296
+ use_dynamic_shifting=False,
297
+ )
298
+ sample_scheduler.set_timesteps(sampling_steps, device=self.device, shift=shift)
299
+ timesteps = sample_scheduler.timesteps
300
+
301
+ latent = noise
302
+
303
+ arg_c = {
304
+ "context": [context[0]],
305
+ "seq_len": max_seq_len,
306
+ "y": [y],
307
+ "dit_cond_dict": dit_cond_dict,
308
+ }
309
+
310
+ arg_null = {
311
+ "context": context_null,
312
+ "seq_len": max_seq_len,
313
+ "y": [y],
314
+ "dit_cond_dict": dit_cond_dict,
315
+ }
316
+
317
+ torch.cuda.empty_cache()
318
+
319
+ # Pre-load first model
320
+ first_model_name = (
321
+ "high_noise_model" if timesteps[0].item() >= boundary else "low_noise_model"
322
+ )
323
+ getattr(self, first_model_name).to(self.device)
324
+ logger.info(f"Loaded {first_model_name} to GPU")
325
+
326
+ for _, t in enumerate(tqdm(timesteps, desc="Sampling")):
327
+ latent_model_input = [latent.to(self.device)]
328
+ timestep = torch.stack([t]).to(self.device)
329
+
330
+ model = self._prepare_model_for_timestep(t, boundary)
331
+ sample_guide_scale = (
332
+ guide_scale[1] if t.item() >= boundary else guide_scale[0]
333
+ )
334
+
335
+ noise_pred_cond = model(latent_model_input, t=timestep, **arg_c)[0]
336
+ torch.cuda.empty_cache()
337
+ noise_pred_uncond = model(latent_model_input, t=timestep, **arg_null)[0]
338
+ torch.cuda.empty_cache()
339
+ noise_pred = noise_pred_uncond + sample_guide_scale * (
340
+ noise_pred_cond - noise_pred_uncond
341
+ )
342
+
343
+ temp_x0 = sample_scheduler.step(
344
+ noise_pred.unsqueeze(0),
345
+ t,
346
+ latent.unsqueeze(0),
347
+ return_dict=False,
348
+ generator=seed_g,
349
+ )[0]
350
+ latent = temp_x0.squeeze(0)
351
+
352
+ # Offload models
353
+ self.low_noise_model.cpu()
354
+ self.high_noise_model.cpu()
355
+ torch.cuda.empty_cache()
356
+
357
+ # Decode video
358
+ videos = self.vae.decode([latent])
359
+
360
+ del noise, latent
361
+ gc.collect()
362
+ torch.cuda.synchronize()
363
+
364
+ return videos[0]
365
+
366
+
367
+ def save_video(frames: torch.Tensor, output_path: str, fps: int = 16):
368
+ """Save video frames to file."""
369
+ import imageio
370
+
371
+ frames = ((frames + 1) / 2 * 255).clamp(0, 255).byte()
372
+ frames = frames.permute(1, 2, 3, 0).cpu().numpy()
373
+
374
+ imageio.mimwrite(output_path, frames, fps=fps, codec="libx264")
375
+ logger.info(f"Saved video to {output_path}")
376
+
377
+
378
+ def main():
379
+ parser = argparse.ArgumentParser(
380
+ description="Generate videos with pre-quantized NF4 models"
381
+ )
382
+ # Default to current directory (for self-contained HuggingFace repo)
383
+ script_dir = str(Path(__file__).parent)
384
+ parser.add_argument("--ckpt_dir", type=str, default=script_dir)
385
+ parser.add_argument("--image", type=str, required=True, help="Input image path")
386
+ parser.add_argument("--prompt", type=str, required=True, help="Text prompt")
387
+ parser.add_argument(
388
+ "--action_path", type=str, default=None, help="Camera control path"
389
+ )
390
+ parser.add_argument("--size", type=str, default="480*832", help="Output resolution")
391
+ parser.add_argument("--frame_num", type=int, default=81)
392
+ parser.add_argument("--sampling_steps", type=int, default=40)
393
+ parser.add_argument("--guide_scale", type=float, default=5.0)
394
+ parser.add_argument("--seed", type=int, default=-1)
395
+ parser.add_argument("--output", type=str, default="output.mp4")
396
+ parser.add_argument("--t5_cpu", action="store_true", default=True)
397
+ args = parser.parse_args()
398
+
399
+ h, w = map(int, args.size.split("*"))
400
+ max_area = h * w
401
+
402
+ img = Image.open(args.image).convert("RGB")
403
+
404
+ pipeline = WanI2V_PreQuant(
405
+ checkpoint_dir=args.ckpt_dir,
406
+ t5_cpu=args.t5_cpu,
407
+ )
408
+
409
+ logger.info("Generating video...")
410
+ video = pipeline.generate(
411
+ input_prompt=args.prompt,
412
+ img=img,
413
+ action_path=args.action_path,
414
+ max_area=max_area,
415
+ frame_num=args.frame_num,
416
+ sampling_steps=args.sampling_steps,
417
+ guide_scale=args.guide_scale,
418
+ seed=args.seed,
419
+ )
420
+
421
+ save_video(video, args.output)
422
+
423
+
424
+ if __name__ == "__main__":
425
+ main()
high_noise_model_bnb_nf4/config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "WanModel",
3
+ "_diffusers_version": "0.36.0",
4
+ "_name_or_path": "lingbot-world-base-cam",
5
+ "model_type": "i2v",
6
+ "text_len": 512,
7
+ "in_dim": 36,
8
+ "dim": 5120,
9
+ "ffn_dim": 13824,
10
+ "freq_dim": 256,
11
+ "out_dim": 16,
12
+ "num_heads": 40,
13
+ "num_layers": 40,
14
+ "eps": 1e-06
15
+ }
high_noise_model_bnb_nf4/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9319e7bccffcc02baf23f1ed4f341423c73ee5f4409cf5700602ecacca87fcb6
3
+ size 9577265260
high_noise_model_bnb_nf4/quantization_meta.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source": {
3
+ "ckpt_dir": "lingbot-world-base-cam",
4
+ "subfolder": "high_noise_model"
5
+ },
6
+ "model_config": {
7
+ "model_type": "i2v",
8
+ "patch_size": [
9
+ 1,
10
+ 2,
11
+ 2
12
+ ],
13
+ "text_len": 512,
14
+ "in_dim": 36,
15
+ "dim": 5120,
16
+ "ffn_dim": 13824,
17
+ "freq_dim": 256,
18
+ "text_dim": 4096,
19
+ "out_dim": 16,
20
+ "num_heads": 40,
21
+ "num_layers": 40,
22
+ "window_size": [
23
+ -1,
24
+ -1
25
+ ],
26
+ "qk_norm": true,
27
+ "cross_attn_norm": true,
28
+ "eps": 1e-06
29
+ },
30
+ "quant": {
31
+ "format": "bnb_nf4",
32
+ "double_quant": true,
33
+ "compute_dtype": "bfloat16",
34
+ "quant_type": "nf4"
35
+ },
36
+ "files": {
37
+ "weights": "model.safetensors",
38
+ "config": "config.json"
39
+ },
40
+ "sizes": {
41
+ "original_bytes": 37088665728,
42
+ "quantized_bytes": 9577265260,
43
+ "compression_ratio": 3.87
44
+ },
45
+ "versions": {
46
+ "python": "3.10.12",
47
+ "torch": "2.10.0+cu130",
48
+ "cuda": "13.0",
49
+ "bitsandbytes": "0.49.1",
50
+ "platform": "Linux-6.8.0-94-generic-x86_64-with-glibc2.35",
51
+ "git_commit": "fd9c95769b72"
52
+ },
53
+ "created_at": "2026-02-03T06:16:38.006296Z"
54
+ }
load_prequant.py ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Load pre-quantized bitsandbytes NF4 models without needing base weights.
4
+
5
+ This module provides utilities to load pre-quantized WanModel weights
6
+ directly from safetensors/pt files, without downloading or loading
7
+ the original FP16/BF16 base model weights.
8
+
9
+ Usage:
10
+ from load_prequant import load_quantized_model
11
+
12
+ model = load_quantized_model("lingbot-world-base-cam/high_noise_model_bnb_nf4")
13
+ """
14
+
15
+ import json
16
+ import os
17
+ from pathlib import Path
18
+ from typing import Optional, Dict, Any, Tuple
19
+ from collections import defaultdict
20
+
21
+ import torch
22
+ import torch.nn as nn
23
+ import bitsandbytes as bnb
24
+ from bitsandbytes.functional import QuantState
25
+
26
+ # Add parent to path for wan imports
27
+ import sys
28
+ sys.path.insert(0, str(Path(__file__).parent))
29
+
30
+ from wan.modules.model import WanModel
31
+
32
+
33
+ def replace_linears_with_bnb_nf4(
34
+ model: nn.Module,
35
+ compute_dtype: torch.dtype = torch.bfloat16,
36
+ compress_statistics: bool = True,
37
+ quant_type: str = "nf4",
38
+ ) -> Tuple[int, Dict[str, Tuple[int, int]]]:
39
+ """
40
+ Replace all nn.Linear layers with empty bnb.nn.Linear4bit layers.
41
+
42
+ This creates the structure needed to load pre-quantized weights.
43
+ The layers are created without weights - they will be populated
44
+ by load_state_dict afterwards.
45
+
46
+ Args:
47
+ model: The model to modify in-place
48
+ compute_dtype: Compute dtype for the quantized layers
49
+ compress_statistics: Whether to use double quantization
50
+ quant_type: Quantization type ('nf4' or 'fp4')
51
+
52
+ Returns:
53
+ Tuple of (num_replaced, dict mapping layer_name to (in_features, out_features))
54
+ """
55
+ replaced = 0
56
+ layer_shapes = {}
57
+
58
+ # Collect all linear layers first to avoid modifying during iteration
59
+ linear_layers = []
60
+ for name, module in model.named_modules():
61
+ if isinstance(module, nn.Linear):
62
+ linear_layers.append((name, module))
63
+
64
+ for name, module in linear_layers:
65
+ # Get parent module
66
+ parent_name = '.'.join(name.split('.')[:-1])
67
+ child_name = name.split('.')[-1]
68
+
69
+ if parent_name:
70
+ parent = model.get_submodule(parent_name)
71
+ else:
72
+ parent = model
73
+
74
+ # Store original shape for reconstruction
75
+ layer_shapes[name] = (module.in_features, module.out_features)
76
+
77
+ # Create empty NF4 linear layer with same shape
78
+ nf4_linear = bnb.nn.Linear4bit(
79
+ module.in_features,
80
+ module.out_features,
81
+ bias=module.bias is not None,
82
+ compute_dtype=compute_dtype,
83
+ compress_statistics=compress_statistics,
84
+ quant_type=quant_type,
85
+ )
86
+
87
+ # Replace the layer (weights will be loaded from state_dict)
88
+ setattr(parent, child_name, nf4_linear)
89
+ replaced += 1
90
+
91
+ return replaced, layer_shapes
92
+
93
+
94
+ def build_model_from_config(config: Dict[str, Any]) -> WanModel:
95
+ """
96
+ Build a WanModel instance from config dictionary.
97
+
98
+ Args:
99
+ config: Dictionary with model configuration
100
+
101
+ Returns:
102
+ Uninitialized WanModel instance
103
+ """
104
+ # Extract config values with defaults matching WanModel.__init__
105
+ model = WanModel(
106
+ model_type=config.get("model_type", "i2v"),
107
+ patch_size=tuple(config.get("patch_size", (1, 2, 2))),
108
+ text_len=config.get("text_len", 512),
109
+ in_dim=config.get("in_dim", 16),
110
+ dim=config.get("dim", 2048),
111
+ ffn_dim=config.get("ffn_dim", 8192),
112
+ freq_dim=config.get("freq_dim", 256),
113
+ text_dim=config.get("text_dim", 4096),
114
+ out_dim=config.get("out_dim", 16),
115
+ num_heads=config.get("num_heads", 16),
116
+ num_layers=config.get("num_layers", 32),
117
+ window_size=tuple(config.get("window_size", (-1, -1))),
118
+ qk_norm=config.get("qk_norm", True),
119
+ cross_attn_norm=config.get("cross_attn_norm", True),
120
+ eps=config.get("eps", 1e-6),
121
+ )
122
+
123
+ return model
124
+
125
+
126
+ def reconstruct_params4bit_from_components(
127
+ weight_components: Dict[str, torch.Tensor],
128
+ device: str = "cuda",
129
+ ) -> bnb.nn.Params4bit:
130
+ """
131
+ Reconstruct a Params4bit object from serialized components using QuantState.from_dict.
132
+
133
+ This uses bitsandbytes' own deserialization method for correctness.
134
+
135
+ Args:
136
+ weight_components: Dict with keys like 'weight', 'absmax', 'quant_map',
137
+ 'nested_absmax', 'nested_quant_map', 'quant_state_data'
138
+ device: Device to load to
139
+
140
+ Returns:
141
+ Reconstructed Params4bit
142
+ """
143
+ # Build the dict that QuantState.from_dict expects
144
+ qs_dict = {
145
+ "absmax": weight_components["absmax"],
146
+ "quant_map": weight_components["quant_map"],
147
+ }
148
+
149
+ # Add nested quantization components if present (double quantization)
150
+ if "nested_absmax" in weight_components:
151
+ qs_dict["nested_absmax"] = weight_components["nested_absmax"]
152
+ qs_dict["nested_quant_map"] = weight_components["nested_quant_map"]
153
+
154
+ # Add the packed quant_state data (contains shape, dtype, etc.)
155
+ if "quant_state_data" in weight_components:
156
+ qs_dict["quant_state.bitsandbytes__nf4"] = weight_components["quant_state_data"]
157
+
158
+ # Use bitsandbytes' own deserialization
159
+ quant_state = QuantState.from_dict(qs_dict, device=torch.device(device))
160
+
161
+ # Get quantized weight and move to device
162
+ quantized_weight = weight_components["weight"].to(device)
163
+
164
+ # Create Params4bit with the quantized data
165
+ param = bnb.nn.Params4bit(
166
+ data=quantized_weight,
167
+ requires_grad=False,
168
+ quant_state=quant_state,
169
+ bnb_quantized=True, # Already quantized, don't re-quantize on .to()
170
+ )
171
+
172
+ return param
173
+
174
+
175
+ def load_quantized_state(
176
+ model: nn.Module,
177
+ weights_path: str,
178
+ layer_shapes: Dict[str, Tuple[int, int]],
179
+ device: str = "cpu",
180
+ ) -> nn.Module:
181
+ """
182
+ Load quantized weights into a model with bnb.Linear4bit layers.
183
+
184
+ This function handles the special bitsandbytes serialization format
185
+ where weights are decomposed into quantized data + metadata tensors.
186
+
187
+ Uses QuantState.from_dict for proper deserialization.
188
+
189
+ Args:
190
+ model: Model with Linear4bit layers already in place
191
+ weights_path: Path to model.safetensors or model.pt
192
+ layer_shapes: Dict mapping layer names to (in_features, out_features)
193
+ device: Device to load quantized weights to
194
+
195
+ Returns:
196
+ Model with loaded weights
197
+ """
198
+ if weights_path.endswith(".safetensors"):
199
+ from safetensors.torch import load_file
200
+ sd = load_file(weights_path)
201
+ else:
202
+ sd = torch.load(weights_path, map_location="cpu", weights_only=False)
203
+
204
+ # Group keys by their base weight name
205
+ weight_components = defaultdict(dict)
206
+ other_keys = {}
207
+
208
+ # Quantization-related suffixes
209
+ quant_suffixes = [".absmax", ".quant_map", ".nested_absmax", ".nested_quant_map", ".quant_state.bitsandbytes__nf4"]
210
+
211
+ for key, tensor in sd.items():
212
+ base_key = None
213
+ component = None
214
+
215
+ if ".weight.absmax" in key:
216
+ base_key = key.replace(".weight.absmax", "")
217
+ component = "absmax"
218
+ elif ".weight.quant_map" in key:
219
+ base_key = key.replace(".weight.quant_map", "")
220
+ component = "quant_map"
221
+ elif ".weight.nested_absmax" in key:
222
+ base_key = key.replace(".weight.nested_absmax", "")
223
+ component = "nested_absmax"
224
+ elif ".weight.nested_quant_map" in key:
225
+ base_key = key.replace(".weight.nested_quant_map", "")
226
+ component = "nested_quant_map"
227
+ elif ".weight.quant_state.bitsandbytes__nf4" in key:
228
+ base_key = key.replace(".weight.quant_state.bitsandbytes__nf4", "")
229
+ component = "quant_state_data"
230
+ elif key.endswith(".weight"):
231
+ # Check if this is a quantized linear weight or regular weight
232
+ potential_base = key[:-7] # Remove ".weight"
233
+ has_quant_metadata = any(f"{potential_base}.weight{suffix}" in sd for suffix in quant_suffixes)
234
+
235
+ if has_quant_metadata:
236
+ base_key = potential_base
237
+ component = "weight"
238
+ else:
239
+ other_keys[key] = tensor
240
+ continue
241
+ else:
242
+ other_keys[key] = tensor
243
+ continue
244
+
245
+ if base_key and component:
246
+ weight_components[base_key][component] = tensor
247
+
248
+ # Load quantized weights into model
249
+ loaded_count = 0
250
+ for name, module in model.named_modules():
251
+ if isinstance(module, bnb.nn.Linear4bit):
252
+ if name in weight_components and name in layer_shapes:
253
+ components = weight_components[name]
254
+
255
+ if "weight" in components:
256
+ # Use bitsandbytes' own deserialization via QuantState.from_dict
257
+ param = reconstruct_params4bit_from_components(components, device=device)
258
+ module.weight = param
259
+ loaded_count += 1
260
+
261
+ # Load bias if present
262
+ bias_key = f"{name}.bias"
263
+ if bias_key in other_keys and module.bias is not None:
264
+ module.bias.data.copy_(other_keys[bias_key].to(device))
265
+
266
+ # Load non-quantized weights (embeddings, norms, biases, etc.)
267
+ non_linear_sd = {}
268
+ for key, tensor in other_keys.items():
269
+ non_linear_sd[key] = tensor
270
+
271
+ if non_linear_sd:
272
+ missing, unexpected = model.load_state_dict(non_linear_sd, strict=False)
273
+ expected_missing = {f"{name}.weight" for name in layer_shapes.keys()}
274
+ critical_missing = [k for k in missing if k not in expected_missing and not k.endswith("freqs")]
275
+ if critical_missing:
276
+ print(f"Warning: Missing non-quantized keys: {critical_missing[:10]}...")
277
+
278
+ print(f" Loaded {loaded_count} quantized linear layers")
279
+ return model
280
+
281
+
282
+ def load_quantized_model(
283
+ model_dir: str,
284
+ device: str = "cuda",
285
+ compute_dtype: torch.dtype = torch.bfloat16,
286
+ ) -> WanModel:
287
+ """
288
+ Load a pre-quantized WanModel from a directory.
289
+
290
+ This function:
291
+ 1. Reads config.json to get model architecture
292
+ 2. Builds an empty WanModel
293
+ 3. Replaces Linear layers with bnb.Linear4bit
294
+ 4. Loads the pre-quantized weights with proper reconstruction
295
+ 5. Moves model to device
296
+
297
+ Args:
298
+ model_dir: Directory containing config.json and model.safetensors/model.pt
299
+ device: Device to load model to
300
+ compute_dtype: Compute dtype for quantized layers
301
+
302
+ Returns:
303
+ Loaded and ready WanModel
304
+ """
305
+ model_dir = Path(model_dir)
306
+
307
+ # Load config
308
+ config_path = model_dir / "config.json"
309
+ if not config_path.exists():
310
+ raise FileNotFoundError(f"Config not found: {config_path}")
311
+
312
+ with open(config_path, "r") as f:
313
+ config = json.load(f)
314
+
315
+ # Check for quantization metadata (optional but recommended)
316
+ meta_path = model_dir / "quantization_meta.json"
317
+ if meta_path.exists():
318
+ with open(meta_path, "r") as f:
319
+ meta = json.load(f)
320
+ quant_config = meta.get("quant", {})
321
+ compute_dtype_str = quant_config.get("compute_dtype", "bfloat16")
322
+ compute_dtype = getattr(torch, compute_dtype_str, torch.bfloat16)
323
+
324
+ # Find weights file (prefer safetensors)
325
+ safetensors_path = model_dir / "model.safetensors"
326
+ pt_path = model_dir / "model.pt"
327
+
328
+ if safetensors_path.exists():
329
+ weights_path = str(safetensors_path)
330
+ elif pt_path.exists():
331
+ weights_path = str(pt_path)
332
+ else:
333
+ raise FileNotFoundError(
334
+ f"No weights found in {model_dir}. "
335
+ "Expected model.safetensors or model.pt"
336
+ )
337
+
338
+ print(f"Loading pre-quantized model from {model_dir}")
339
+ print(f" Config: {config_path}")
340
+ print(f" Weights: {weights_path}")
341
+
342
+ # Build model from config (creates initialized weights we'll replace)
343
+ model = build_model_from_config(config)
344
+
345
+ # Replace Linear → Linear4bit (empty, ready for state_dict)
346
+ replaced, layer_shapes = replace_linears_with_bnb_nf4(model, compute_dtype=compute_dtype)
347
+ print(f" Replaced {replaced} linear layers with bnb.Linear4bit")
348
+
349
+ # Load quantized weights with proper reconstruction
350
+ # This loads quantized weights directly to the target device
351
+ model = load_quantized_state(model, weights_path, layer_shapes, device=device)
352
+
353
+ # Move non-quantized parts to device and set eval mode
354
+ model.to(device)
355
+ model.eval()
356
+ model.requires_grad_(False)
357
+
358
+ print(f" Model ready on {device}")
359
+
360
+ return model
361
+
362
+
363
+ def verify_quantized_model(model: nn.Module) -> Dict[str, Any]:
364
+ """
365
+ Verify that a model has been properly quantized.
366
+
367
+ Args:
368
+ model: Model to verify
369
+
370
+ Returns:
371
+ Dictionary with verification results
372
+ """
373
+ total_params = 0
374
+ quantized_params = 0
375
+ linear4bit_count = 0
376
+ regular_linear_count = 0
377
+
378
+ for name, module in model.named_modules():
379
+ if isinstance(module, bnb.nn.Linear4bit):
380
+ linear4bit_count += 1
381
+ if hasattr(module.weight, 'quant_state') and module.weight.quant_state is not None:
382
+ quantized_params += module.weight.numel()
383
+ elif isinstance(module, nn.Linear):
384
+ regular_linear_count += 1
385
+
386
+ for param in model.parameters():
387
+ total_params += param.numel()
388
+
389
+ return {
390
+ "total_params": total_params,
391
+ "quantized_params": quantized_params,
392
+ "linear4bit_count": linear4bit_count,
393
+ "regular_linear_count": regular_linear_count,
394
+ "is_quantized": linear4bit_count > 0 and regular_linear_count == 0,
395
+ }
396
+
397
+
398
+ if __name__ == "__main__":
399
+ import argparse
400
+
401
+ parser = argparse.ArgumentParser(description="Test loading pre-quantized model")
402
+ parser.add_argument("model_dir", type=str, help="Path to quantized model directory")
403
+ parser.add_argument("--device", type=str, default="cuda", help="Device to load to")
404
+ args = parser.parse_args()
405
+
406
+ model = load_quantized_model(args.model_dir, device=args.device)
407
+
408
+ info = verify_quantized_model(model)
409
+ print(f"\nVerification:")
410
+ print(f" Linear4bit layers: {info['linear4bit_count']}")
411
+ print(f" Regular Linear layers: {info['regular_linear_count']}")
412
+ print(f" Is properly quantized: {info['is_quantized']}")
low_noise_model_bnb_nf4/config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "WanModel",
3
+ "_diffusers_version": "0.36.0",
4
+ "_name_or_path": "lingbot-world-base-cam",
5
+ "model_type": "i2v",
6
+ "text_len": 512,
7
+ "in_dim": 36,
8
+ "dim": 5120,
9
+ "ffn_dim": 13824,
10
+ "freq_dim": 256,
11
+ "out_dim": 16,
12
+ "num_heads": 40,
13
+ "num_layers": 40,
14
+ "eps": 1e-06
15
+ }
low_noise_model_bnb_nf4/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bd898a2770300c34c7092b64a6a6ed94ca881223473c65f37fbbb04d88e3bb56
3
+ size 9577265273
low_noise_model_bnb_nf4/quantization_meta.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source": {
3
+ "ckpt_dir": "lingbot-world-base-cam",
4
+ "subfolder": "low_noise_model"
5
+ },
6
+ "model_config": {
7
+ "model_type": "i2v",
8
+ "patch_size": [
9
+ 1,
10
+ 2,
11
+ 2
12
+ ],
13
+ "text_len": 512,
14
+ "in_dim": 36,
15
+ "dim": 5120,
16
+ "ffn_dim": 13824,
17
+ "freq_dim": 256,
18
+ "text_dim": 4096,
19
+ "out_dim": 16,
20
+ "num_heads": 40,
21
+ "num_layers": 40,
22
+ "window_size": [
23
+ -1,
24
+ -1
25
+ ],
26
+ "qk_norm": true,
27
+ "cross_attn_norm": true,
28
+ "eps": 1e-06
29
+ },
30
+ "quant": {
31
+ "format": "bnb_nf4",
32
+ "double_quant": true,
33
+ "compute_dtype": "bfloat16",
34
+ "quant_type": "nf4"
35
+ },
36
+ "files": {
37
+ "weights": "model.safetensors",
38
+ "config": "config.json"
39
+ },
40
+ "sizes": {
41
+ "original_bytes": 37088665728,
42
+ "quantized_bytes": 9577265273,
43
+ "compression_ratio": 3.87
44
+ },
45
+ "versions": {
46
+ "python": "3.10.12",
47
+ "torch": "2.10.0+cu130",
48
+ "cuda": "13.0",
49
+ "bitsandbytes": "0.49.1",
50
+ "platform": "Linux-6.8.0-94-generic-x86_64-with-glibc2.35",
51
+ "git_commit": "fd9c95769b72"
52
+ },
53
+ "created_at": "2026-02-03T06:19:46.213374Z"
54
+ }
models_t5_umt5-xxl-enc-bf16.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7cace0da2b446bbbbc57d031ab6cf163a3d59b366da94e5afe36745b746fd81d
3
+ size 11361920418
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=2.0.0
2
+ torchvision
3
+ torchaudio
4
+ bitsandbytes>=0.43.0
5
+ safetensors
6
+ accelerate
7
+ transformers
8
+ diffusers
9
+ einops
10
+ imageio
11
+ imageio-ffmpeg
12
+ pillow
13
+ numpy
14
+ tqdm
15
+ easydict
tokenizer/special_tokens_map.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:456b58fd240a06c743a7c2cf8008bec501240d68ebd1fc4018ea569505fea270
3
+ size 7079
tokenizer/tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:20a46ac256746594ed7e1e3ef733b83fbc5a6f0922aa7480eda961743de080ef
3
+ size 16837459
tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89bef11d01c80a229079666dd470d536aba87b8eaa00b51cc481ba625be59269
3
+ size 61786
wan/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from . import configs, distributed, modules
2
+ from .image2video import WanI2V
wan/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (274 Bytes). View file
 
wan/__pycache__/image2video.cpython-310.pyc ADDED
Binary file (13.5 kB). View file
 
wan/configs/__init__.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ import os
3
+
4
+ os.environ['TOKENIZERS_PARALLELISM'] = 'false'
5
+
6
+ from .wan_i2v_A14B import i2v_A14B
7
+
8
+ WAN_CONFIGS = {
9
+ 'i2v-A14B': i2v_A14B,
10
+ }
11
+
12
+ SIZE_CONFIGS = {
13
+ '720*1280': (720, 1280),
14
+ '1280*720': (1280, 720),
15
+ '480*832': (480, 832),
16
+ '832*480': (832, 480),
17
+ '704*1280': (704, 1280),
18
+ '1280*704': (1280, 704),
19
+ '1024*704': (1024, 704),
20
+ '704*1024': (704, 1024),
21
+ }
22
+
23
+ MAX_AREA_CONFIGS = {
24
+ '720*1280': 720 * 1280,
25
+ '1280*720': 1280 * 720,
26
+ '480*832': 480 * 832,
27
+ '832*480': 832 * 480,
28
+ '704*1280': 704 * 1280,
29
+ '1280*704': 1280 * 704,
30
+ '1024*704': 1024 * 704,
31
+ '704*1024': 704 * 1024,
32
+ }
33
+
34
+ SUPPORTED_SIZES = {
35
+ 'i2v-A14B': ('720*1280', '1280*720', '480*832', '832*480')
36
+ }
wan/configs/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (694 Bytes). View file
 
wan/configs/__pycache__/shared_config.cpython-310.pyc ADDED
Binary file (877 Bytes). View file
 
wan/configs/__pycache__/wan_i2v_A14B.cpython-310.pyc ADDED
Binary file (1.77 kB). View file
 
wan/configs/shared_config.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from easydict import EasyDict
3
+
4
+ #------------------------ Wan shared config ------------------------#
5
+ wan_shared_cfg = EasyDict()
6
+
7
+ # t5
8
+ wan_shared_cfg.t5_model = 'umt5_xxl'
9
+ wan_shared_cfg.t5_dtype = torch.bfloat16
10
+ wan_shared_cfg.text_len = 512
11
+
12
+ # transformer
13
+ wan_shared_cfg.param_dtype = torch.bfloat16
14
+
15
+ # inference
16
+ wan_shared_cfg.num_train_timesteps = 1000
17
+ wan_shared_cfg.sample_fps = 16
18
+ wan_shared_cfg.sample_neg_prompt = '色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走'
19
+ wan_shared_cfg.frame_num = 81
wan/configs/wan_i2v_A14B.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from easydict import EasyDict
3
+
4
+ from .shared_config import wan_shared_cfg
5
+
6
+ #------------------------ Wan I2V A14B ------------------------#
7
+
8
+ i2v_A14B = EasyDict(__name__='Config: Wan I2V A14B')
9
+ i2v_A14B.update(wan_shared_cfg)
10
+
11
+ i2v_A14B.t5_checkpoint = 'models_t5_umt5-xxl-enc-bf16.pth'
12
+ i2v_A14B.t5_tokenizer = 'google/umt5-xxl'
13
+
14
+ # vae
15
+ i2v_A14B.vae_checkpoint = 'Wan2.1_VAE.pth'
16
+ i2v_A14B.vae_stride = (4, 8, 8)
17
+
18
+ # transformer
19
+ i2v_A14B.patch_size = (1, 2, 2)
20
+ i2v_A14B.dim = 5120
21
+ i2v_A14B.ffn_dim = 13824
22
+ i2v_A14B.freq_dim = 256
23
+ i2v_A14B.num_heads = 40
24
+ i2v_A14B.num_layers = 40
25
+ i2v_A14B.window_size = (-1, -1)
26
+ i2v_A14B.qk_norm = True
27
+ i2v_A14B.cross_attn_norm = True
28
+ i2v_A14B.eps = 1e-6
29
+ i2v_A14B.low_noise_checkpoint = 'low_noise_model'
30
+ i2v_A14B.high_noise_checkpoint = 'high_noise_model'
31
+
32
+ # inference
33
+ i2v_A14B.sample_shift = 10.0
34
+ i2v_A14B.sample_steps = 70
35
+ i2v_A14B.boundary = 0.947
36
+ i2v_A14B.sample_guide_scale = (5.0, 5.0) # low noise, high noise
37
+ i2v_A14B.sample_neg_prompt = '画面突变,色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走,镜头晃动,画面闪烁,模糊,噪点,水印,签名,文字,变形,扭曲,液化,不合逻辑的结构,卡顿,PPT幻灯片感,过暗,欠曝,低对比度,霓虹灯光感,过度锐化,3D渲染感,人物,行人,游客,身体,皮肤,肢体,面部特征,汽车,电线'
wan/distributed/__init__.py ADDED
File without changes
wan/distributed/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (172 Bytes). View file
 
wan/distributed/__pycache__/fsdp.cpython-310.pyc ADDED
Binary file (1.44 kB). View file
 
wan/distributed/__pycache__/sequence_parallel.cpython-310.pyc ADDED
Binary file (6.13 kB). View file
 
wan/distributed/__pycache__/ulysses.cpython-310.pyc ADDED
Binary file (1.26 kB). View file
 
wan/distributed/__pycache__/util.cpython-310.pyc ADDED
Binary file (1.96 kB). View file
 
wan/distributed/fsdp.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ from functools import partial
3
+
4
+ import torch
5
+ from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
6
+ from torch.distributed.fsdp import MixedPrecision, ShardingStrategy
7
+ from torch.distributed.fsdp.wrap import lambda_auto_wrap_policy
8
+ from torch.distributed.utils import _free_storage
9
+
10
+
11
+ def shard_model(
12
+ model,
13
+ device_id,
14
+ param_dtype=torch.bfloat16,
15
+ reduce_dtype=torch.float32,
16
+ buffer_dtype=torch.float32,
17
+ process_group=None,
18
+ sharding_strategy=ShardingStrategy.FULL_SHARD,
19
+ sync_module_states=True,
20
+ use_lora=False
21
+ ):
22
+ model = FSDP(
23
+ module=model,
24
+ process_group=process_group,
25
+ sharding_strategy=sharding_strategy,
26
+ auto_wrap_policy=partial(
27
+ lambda_auto_wrap_policy, lambda_fn=lambda m: m in model.blocks),
28
+ mixed_precision=MixedPrecision(
29
+ param_dtype=param_dtype,
30
+ reduce_dtype=reduce_dtype,
31
+ buffer_dtype=buffer_dtype),
32
+ device_id=device_id,
33
+ sync_module_states=sync_module_states,
34
+ use_orig_params=True if use_lora else False)
35
+ return model
36
+
37
+
38
+ def free_model(model):
39
+ for m in model.modules():
40
+ if isinstance(m, FSDP):
41
+ _free_storage(m._handle.flat_param.data)
42
+ del model
43
+ gc.collect()
44
+ torch.cuda.empty_cache()
wan/distributed/sequence_parallel.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.cuda.amp as amp
3
+ import torch.nn.functional as torch_F
4
+ from einops import rearrange
5
+
6
+ from ..modules.model import sinusoidal_embedding_1d
7
+ from .ulysses import distributed_attention
8
+ from .util import gather_forward, get_rank, get_world_size
9
+
10
+
11
+ def pad_freqs(original_tensor, target_len):
12
+ seq_len, s1, s2 = original_tensor.shape
13
+ pad_size = target_len - seq_len
14
+ padding_tensor = torch.ones(
15
+ pad_size,
16
+ s1,
17
+ s2,
18
+ dtype=original_tensor.dtype,
19
+ device=original_tensor.device)
20
+ padded_tensor = torch.cat([original_tensor, padding_tensor], dim=0)
21
+ return padded_tensor
22
+
23
+
24
+ @torch.amp.autocast('cuda', enabled=False)
25
+ def rope_apply(x, grid_sizes, freqs):
26
+ """
27
+ x: [B, L, N, C].
28
+ grid_sizes: [B, 3].
29
+ freqs: [M, C // 2].
30
+ """
31
+ s, n, c = x.size(1), x.size(2), x.size(3) // 2
32
+ # split freqs
33
+ freqs = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1)
34
+
35
+ # loop over samples
36
+ output = []
37
+ for i, (f, h, w) in enumerate(grid_sizes.tolist()):
38
+ seq_len = f * h * w
39
+
40
+ # precompute multipliers
41
+ x_i = torch.view_as_complex(x[i, :s].to(torch.float64).reshape(
42
+ s, n, -1, 2))
43
+ freqs_i = torch.cat([
44
+ freqs[0][:f].view(f, 1, 1, -1).expand(f, h, w, -1),
45
+ freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
46
+ freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1)
47
+ ],
48
+ dim=-1).reshape(seq_len, 1, -1)
49
+
50
+ # apply rotary embedding
51
+ sp_size = get_world_size()
52
+ sp_rank = get_rank()
53
+ freqs_i = pad_freqs(freqs_i, s * sp_size)
54
+ s_per_rank = s
55
+ freqs_i_rank = freqs_i[(sp_rank * s_per_rank):((sp_rank + 1) *
56
+ s_per_rank), :, :]
57
+ x_i = torch.view_as_real(x_i * freqs_i_rank).flatten(2)
58
+ x_i = torch.cat([x_i, x[i, s:]])
59
+
60
+ # append to collection
61
+ output.append(x_i)
62
+ return torch.stack(output).float()
63
+
64
+
65
+ def sp_dit_forward(
66
+ self,
67
+ x,
68
+ t,
69
+ context,
70
+ seq_len,
71
+ y=None,
72
+ dit_cond_dict=None,
73
+ ):
74
+ """
75
+ x: A list of videos each with shape [C, T, H, W].
76
+ t: [B].
77
+ context: A list of text embeddings each with shape [L, C].
78
+ """
79
+ if self.model_type == 'i2v':
80
+ assert y is not None
81
+ # params
82
+ device = self.patch_embedding.weight.device
83
+ if self.freqs.device != device:
84
+ self.freqs = self.freqs.to(device)
85
+
86
+ if y is not None:
87
+ x = [torch.cat([u, v], dim=0) for u, v in zip(x, y)]
88
+
89
+ # embeddings
90
+ x = [self.patch_embedding(u.unsqueeze(0)) for u in x]
91
+ grid_sizes = torch.stack(
92
+ [torch.tensor(u.shape[2:], dtype=torch.long) for u in x])
93
+ x = [u.flatten(2).transpose(1, 2) for u in x]
94
+ seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long)
95
+ assert seq_lens.max() <= seq_len
96
+ x = torch.cat([
97
+ torch.cat([u, u.new_zeros(1, seq_len - u.size(1), u.size(2))], dim=1)
98
+ for u in x
99
+ ])
100
+
101
+ # time embeddings
102
+ if t.dim() == 1:
103
+ t = t.expand(t.size(0), seq_len)
104
+ with torch.amp.autocast('cuda', dtype=torch.float32):
105
+ bt = t.size(0)
106
+ t = t.flatten()
107
+ e = self.time_embedding(
108
+ sinusoidal_embedding_1d(self.freq_dim,
109
+ t).unflatten(0, (bt, seq_len)).float())
110
+ e0 = self.time_projection(e).unflatten(2, (6, self.dim))
111
+ assert e.dtype == torch.float32 and e0.dtype == torch.float32
112
+
113
+ # context
114
+ context_lens = None
115
+ context = self.text_embedding(
116
+ torch.stack([
117
+ torch.cat([u, u.new_zeros(self.text_len - u.size(0), u.size(1))])
118
+ for u in context
119
+ ]))
120
+
121
+ # cam
122
+ if dit_cond_dict is not None and "c2ws_plucker_emb" in dit_cond_dict:
123
+ c2ws_plucker_emb = dit_cond_dict["c2ws_plucker_emb"]
124
+ c2ws_plucker_emb = [
125
+ rearrange(
126
+ i,
127
+ '1 c (f c1) (h c2) (w c3) -> 1 (f h w) (c c1 c2 c3)',
128
+ c1=self.patch_size[0],
129
+ c2=self.patch_size[1],
130
+ c3=self.patch_size[2],
131
+ ) for i in c2ws_plucker_emb
132
+ ]
133
+ c2ws_plucker_emb = torch.cat(c2ws_plucker_emb,
134
+ dim=1) # [1, (L1+...+Ln), C]
135
+ c2ws_plucker_emb = self.patch_embedding_wancamctrl(c2ws_plucker_emb)
136
+ c2ws_hidden_states = self.c2ws_hidden_states_layer2(
137
+ torch_F.silu(self.c2ws_hidden_states_layer1(c2ws_plucker_emb)))
138
+ c2ws_plucker_emb = c2ws_plucker_emb + c2ws_hidden_states
139
+
140
+ cam_len = c2ws_plucker_emb.size(1)
141
+ if cam_len < seq_len:
142
+ pad_len = seq_len - cam_len
143
+ pad = c2ws_plucker_emb.new_zeros(
144
+ c2ws_plucker_emb.size(0), pad_len, c2ws_plucker_emb.size(2))
145
+ c2ws_plucker_emb = torch.cat([c2ws_plucker_emb, pad], dim=1)
146
+ elif cam_len > seq_len:
147
+ c2ws_plucker_emb = c2ws_plucker_emb[:, :seq_len, :]
148
+
149
+ if get_world_size() > 1:
150
+ c2ws_plucker_emb = torch.chunk(
151
+ c2ws_plucker_emb, get_world_size(), dim=1)[get_rank()]
152
+ dit_cond_dict = dict(dit_cond_dict)
153
+ dit_cond_dict["c2ws_plucker_emb"] = c2ws_plucker_emb
154
+
155
+ # Context Parallel
156
+ x = torch.chunk(x, get_world_size(), dim=1)[get_rank()]
157
+ e = torch.chunk(e, get_world_size(), dim=1)[get_rank()]
158
+ e0 = torch.chunk(e0, get_world_size(), dim=1)[get_rank()]
159
+
160
+ # arguments
161
+ kwargs = dict(
162
+ e=e0,
163
+ seq_lens=seq_lens,
164
+ grid_sizes=grid_sizes,
165
+ freqs=self.freqs,
166
+ context=context,
167
+ context_lens=context_lens,
168
+ dit_cond_dict=dit_cond_dict)
169
+
170
+ for block in self.blocks:
171
+ x = block(x, **kwargs)
172
+
173
+ # head
174
+ x = self.head(x, e)
175
+
176
+ # Context Parallel
177
+ x = gather_forward(x, dim=1)
178
+
179
+ # unpatchify
180
+ x = self.unpatchify(x, grid_sizes)
181
+ return [u.float() for u in x]
182
+
183
+
184
+ def sp_attn_forward(self, x, seq_lens, grid_sizes, freqs, dtype=torch.bfloat16):
185
+ b, s, n, d = *x.shape[:2], self.num_heads, self.head_dim
186
+ half_dtypes = (torch.float16, torch.bfloat16)
187
+
188
+ def half(x):
189
+ return x if x.dtype in half_dtypes else x.to(dtype)
190
+
191
+ # query, key, value function
192
+ def qkv_fn(x):
193
+ q = self.norm_q(self.q(x)).view(b, s, n, d)
194
+ k = self.norm_k(self.k(x)).view(b, s, n, d)
195
+ v = self.v(x).view(b, s, n, d)
196
+ return q, k, v
197
+
198
+ q, k, v = qkv_fn(x)
199
+ q = rope_apply(q, grid_sizes, freqs)
200
+ k = rope_apply(k, grid_sizes, freqs)
201
+
202
+ x = distributed_attention(
203
+ half(q),
204
+ half(k),
205
+ half(v),
206
+ seq_lens,
207
+ window_size=self.window_size,
208
+ )
209
+
210
+ # output
211
+ x = x.flatten(2)
212
+ x = self.o(x)
213
+ return x
wan/distributed/ulysses.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.distributed as dist
3
+
4
+ from ..modules.attention import flash_attention
5
+ from .util import all_to_all
6
+
7
+
8
+ def distributed_attention(
9
+ q,
10
+ k,
11
+ v,
12
+ seq_lens,
13
+ window_size=(-1, -1),
14
+ ):
15
+ """
16
+ Performs distributed attention based on DeepSpeed Ulysses attention mechanism.
17
+ please refer to https://arxiv.org/pdf/2309.14509
18
+
19
+ Args:
20
+ q: [B, Lq // p, Nq, C1].
21
+ k: [B, Lk // p, Nk, C1].
22
+ v: [B, Lk // p, Nk, C2]. Nq must be divisible by Nk.
23
+ seq_lens: [B], length of each sequence in batch
24
+ window_size: (left right). If not (-1, -1), apply sliding window local attention.
25
+ """
26
+ if not dist.is_initialized():
27
+ raise ValueError("distributed group should be initialized.")
28
+ b = q.shape[0]
29
+
30
+ # gather q/k/v sequence
31
+ q = all_to_all(q, scatter_dim=2, gather_dim=1)
32
+ k = all_to_all(k, scatter_dim=2, gather_dim=1)
33
+ v = all_to_all(v, scatter_dim=2, gather_dim=1)
34
+
35
+ # apply attention
36
+ x = flash_attention(
37
+ q,
38
+ k,
39
+ v,
40
+ k_lens=seq_lens,
41
+ window_size=window_size,
42
+ )
43
+
44
+ # scatter q/k/v sequence
45
+ x = all_to_all(x, scatter_dim=1, gather_dim=2)
46
+ return x
wan/distributed/util.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.distributed as dist
3
+
4
+
5
+ def init_distributed_group():
6
+ """r initialize sequence parallel group.
7
+ """
8
+ if not dist.is_initialized():
9
+ dist.init_process_group(backend='nccl')
10
+
11
+
12
+ def get_rank():
13
+ return dist.get_rank()
14
+
15
+
16
+ def get_world_size():
17
+ return dist.get_world_size()
18
+
19
+
20
+ def all_to_all(x, scatter_dim, gather_dim, group=None, **kwargs):
21
+ """
22
+ `scatter` along one dimension and `gather` along another.
23
+ """
24
+ world_size = get_world_size()
25
+ if world_size > 1:
26
+ inputs = [u.contiguous() for u in x.chunk(world_size, dim=scatter_dim)]
27
+ outputs = [torch.empty_like(u) for u in inputs]
28
+ dist.all_to_all(outputs, inputs, group=group, **kwargs)
29
+ x = torch.cat(outputs, dim=gather_dim).contiguous()
30
+ return x
31
+
32
+
33
+ def all_gather(tensor):
34
+ world_size = dist.get_world_size()
35
+ if world_size == 1:
36
+ return [tensor]
37
+ tensor_list = [torch.empty_like(tensor) for _ in range(world_size)]
38
+ torch.distributed.all_gather(tensor_list, tensor)
39
+ return tensor_list
40
+
41
+
42
+ def gather_forward(input, dim):
43
+ # skip if world_size == 1
44
+ world_size = dist.get_world_size()
45
+ if world_size == 1:
46
+ return input
47
+
48
+ # gather sequence
49
+ output = all_gather(input)
50
+ return torch.cat(output, dim=dim).contiguous()
wan/image2video.py ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import logging
3
+ import math
4
+ import os
5
+ import random
6
+ import sys
7
+ import types
8
+ from contextlib import contextmanager
9
+ from functools import partial
10
+
11
+ import numpy as np
12
+ import torch
13
+ import torch.cuda.amp as amp
14
+ import torch.distributed as dist
15
+ import torchvision.transforms.functional as TF
16
+ from tqdm import tqdm
17
+
18
+ from .distributed.fsdp import shard_model
19
+ from .distributed.sequence_parallel import sp_attn_forward, sp_dit_forward
20
+ from .distributed.util import get_world_size
21
+ from .modules.model import WanModel
22
+ from .modules.t5 import T5EncoderModel
23
+ from .modules.vae2_1 import Wan2_1_VAE
24
+ from .utils.fm_solvers import (
25
+ FlowDPMSolverMultistepScheduler,
26
+ get_sampling_sigmas,
27
+ retrieve_timesteps,
28
+ )
29
+ from .utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
30
+ from .utils.cam_utils import (
31
+ compute_relative_poses,
32
+ interpolate_camera_poses,
33
+ get_plucker_embeddings,
34
+ get_Ks_transformed,
35
+ )
36
+ from einops import rearrange
37
+
38
+
39
+ class WanI2V:
40
+
41
+ def __init__(
42
+ self,
43
+ config,
44
+ checkpoint_dir,
45
+ device_id=0,
46
+ rank=0,
47
+ t5_fsdp=False,
48
+ dit_fsdp=False,
49
+ use_sp=False,
50
+ t5_cpu=False,
51
+ init_on_cpu=True,
52
+ convert_model_dtype=False,
53
+ ):
54
+ r"""
55
+ Initializes the image-to-video generation model components.
56
+
57
+ Args:
58
+ config (EasyDict):
59
+ Object containing model parameters initialized from config.py
60
+ checkpoint_dir (`str`):
61
+ Path to directory containing model checkpoints
62
+ device_id (`int`, *optional*, defaults to 0):
63
+ Id of target GPU device
64
+ rank (`int`, *optional*, defaults to 0):
65
+ Process rank for distributed training
66
+ t5_fsdp (`bool`, *optional*, defaults to False):
67
+ Enable FSDP sharding for T5 model
68
+ dit_fsdp (`bool`, *optional*, defaults to False):
69
+ Enable FSDP sharding for DiT model
70
+ use_sp (`bool`, *optional*, defaults to False):
71
+ Enable distribution strategy of sequence parallel.
72
+ t5_cpu (`bool`, *optional*, defaults to False):
73
+ Whether to place T5 model on CPU. Only works without t5_fsdp.
74
+ init_on_cpu (`bool`, *optional*, defaults to True):
75
+ Enable initializing Transformer Model on CPU. Only works without FSDP or USP.
76
+ convert_model_dtype (`bool`, *optional*, defaults to False):
77
+ Convert DiT model parameters dtype to 'config.param_dtype'.
78
+ Only works without FSDP.
79
+ """
80
+ self.device = torch.device(f"cuda:{device_id}")
81
+ self.config = config
82
+ self.rank = rank
83
+ self.t5_cpu = t5_cpu
84
+ self.init_on_cpu = init_on_cpu
85
+
86
+ self.num_train_timesteps = config.num_train_timesteps
87
+ self.boundary = config.boundary
88
+ self.param_dtype = config.param_dtype
89
+
90
+ if t5_fsdp or dit_fsdp or use_sp:
91
+ self.init_on_cpu = False
92
+
93
+ shard_fn = partial(shard_model, device_id=device_id)
94
+ self.text_encoder = T5EncoderModel(
95
+ text_len=config.text_len,
96
+ dtype=config.t5_dtype,
97
+ device=torch.device('cpu'),
98
+ checkpoint_path=os.path.join(checkpoint_dir, config.t5_checkpoint),
99
+ tokenizer_path=os.path.join(checkpoint_dir, config.t5_tokenizer),
100
+ shard_fn=shard_fn if t5_fsdp else None,
101
+ )
102
+
103
+ self.vae_stride = config.vae_stride
104
+ self.patch_size = config.patch_size
105
+ self.vae = Wan2_1_VAE(
106
+ vae_pth=os.path.join(checkpoint_dir, config.vae_checkpoint),
107
+ device=self.device)
108
+
109
+ logging.info(f"Creating WanModel from {checkpoint_dir}")
110
+ # Load models with low_cpu_mem_usage to allow CPU loading for single GPU setups
111
+ self.low_noise_model = WanModel.from_pretrained(
112
+ checkpoint_dir, subfolder=config.low_noise_checkpoint, torch_dtype=torch.bfloat16,
113
+ low_cpu_mem_usage=True)
114
+ self.low_noise_model = self._configure_model(
115
+ model=self.low_noise_model,
116
+ use_sp=use_sp,
117
+ dit_fsdp=dit_fsdp,
118
+ shard_fn=shard_fn,
119
+ convert_model_dtype=convert_model_dtype)
120
+
121
+ self.high_noise_model = WanModel.from_pretrained(
122
+ checkpoint_dir, subfolder=config.high_noise_checkpoint, torch_dtype=torch.bfloat16,
123
+ low_cpu_mem_usage=True)
124
+ self.high_noise_model = self._configure_model(
125
+ model=self.high_noise_model,
126
+ use_sp=use_sp,
127
+ dit_fsdp=dit_fsdp,
128
+ shard_fn=shard_fn,
129
+ convert_model_dtype=convert_model_dtype)
130
+ if use_sp:
131
+ self.sp_size = get_world_size()
132
+ else:
133
+ self.sp_size = 1
134
+
135
+ self.sample_neg_prompt = config.sample_neg_prompt
136
+
137
+ def _configure_model(self, model, use_sp, dit_fsdp, shard_fn,
138
+ convert_model_dtype):
139
+ """
140
+ Configures a model object. This includes setting evaluation modes,
141
+ applying distributed parallel strategy, and handling device placement.
142
+
143
+ Args:
144
+ model (torch.nn.Module):
145
+ The model instance to configure.
146
+ use_sp (`bool`):
147
+ Enable distribution strategy of sequence parallel.
148
+ dit_fsdp (`bool`):
149
+ Enable FSDP sharding for DiT model.
150
+ shard_fn (callable):
151
+ The function to apply FSDP sharding.
152
+ convert_model_dtype (`bool`):
153
+ Convert DiT model parameters dtype to 'config.param_dtype'.
154
+ Only works without FSDP.
155
+
156
+ Returns:
157
+ torch.nn.Module:
158
+ The configured model.
159
+ """
160
+ model.eval().requires_grad_(False)
161
+
162
+ if use_sp:
163
+ for block in model.blocks:
164
+ block.self_attn.forward = types.MethodType(
165
+ sp_attn_forward, block.self_attn)
166
+ model.forward = types.MethodType(sp_dit_forward, model)
167
+
168
+ if dist.is_initialized():
169
+ dist.barrier()
170
+
171
+ if dit_fsdp:
172
+ model = shard_fn(model)
173
+ else:
174
+ if convert_model_dtype:
175
+ model.to(self.param_dtype)
176
+ if not self.init_on_cpu:
177
+ model.to(self.device)
178
+
179
+ return model
180
+
181
+ def _prepare_model_for_timestep(self, t, boundary, offload_model):
182
+ r"""
183
+ Prepares and returns the required model for the current timestep.
184
+
185
+ Args:
186
+ t (torch.Tensor):
187
+ current timestep.
188
+ boundary (`int`):
189
+ The timestep threshold. If `t` is at or above this value,
190
+ the `high_noise_model` is considered as the required model.
191
+ offload_model (`bool`):
192
+ A flag intended to control the offloading behavior.
193
+
194
+ Returns:
195
+ torch.nn.Module:
196
+ The active model on the target device for the current timestep.
197
+ """
198
+ if t.item() >= boundary:
199
+ required_model_name = 'high_noise_model'
200
+ offload_model_name = 'low_noise_model'
201
+ else:
202
+ required_model_name = 'low_noise_model'
203
+ offload_model_name = 'high_noise_model'
204
+ if offload_model or self.init_on_cpu:
205
+ if next(getattr(
206
+ self,
207
+ offload_model_name).parameters()).device.type == 'cuda':
208
+ getattr(self, offload_model_name).to('cpu')
209
+ if next(getattr(
210
+ self,
211
+ required_model_name).parameters()).device.type == 'cpu':
212
+ getattr(self, required_model_name).to(self.device)
213
+ return getattr(self, required_model_name)
214
+
215
+ def generate(self,
216
+ input_prompt,
217
+ img,
218
+ action_path=None,
219
+ max_area=720 * 1280,
220
+ frame_num=81,
221
+ shift=5.0,
222
+ sample_solver='unipc',
223
+ sampling_steps=40,
224
+ guide_scale=5.0,
225
+ n_prompt="",
226
+ seed=-1,
227
+ offload_model=True):
228
+ r"""
229
+ Generates video frames from input image and text prompt using diffusion process.
230
+
231
+ Args:
232
+ input_prompt (`str`):
233
+ Text prompt for content generation.
234
+ img (PIL.Image.Image):
235
+ Input image tensor. Shape: [3, H, W]
236
+ max_area (`int`, *optional*, defaults to 720*1280):
237
+ Maximum pixel area for latent space calculation. Controls video resolution scaling
238
+ frame_num (`int`, *optional*, defaults to 81):
239
+ How many frames to sample from a video. The number should be 4n+1
240
+ shift (`float`, *optional*, defaults to 5.0):
241
+ Noise schedule shift parameter. Affects temporal dynamics
242
+ [NOTE]: If you want to generate a 480p video, it is recommended to set the shift value to 3.0.
243
+ sample_solver (`str`, *optional*, defaults to 'unipc'):
244
+ Solver used to sample the video.
245
+ sampling_steps (`int`, *optional*, defaults to 40):
246
+ Number of diffusion sampling steps. Higher values improve quality but slow generation
247
+ guide_scale (`float` or tuple[`float`], *optional*, defaults 5.0):
248
+ Classifier-free guidance scale. Controls prompt adherence vs. creativity.
249
+ If tuple, the first guide_scale will be used for low noise model and
250
+ the second guide_scale will be used for high noise model.
251
+ n_prompt (`str`, *optional*, defaults to ""):
252
+ Negative prompt for content exclusion. If not given, use `config.sample_neg_prompt`
253
+ seed (`int`, *optional*, defaults to -1):
254
+ Random seed for noise generation. If -1, use random seed
255
+ offload_model (`bool`, *optional*, defaults to True):
256
+ If True, offloads models to CPU during generation to save VRAM
257
+
258
+ Returns:
259
+ torch.Tensor:
260
+ Generated video frames tensor. Dimensions: (C, N H, W) where:
261
+ - C: Color channels (3 for RGB)
262
+ - N: Number of frames (81)
263
+ - H: Frame height (from max_area)
264
+ - W: Frame width from max_area)
265
+ """
266
+ if action_path is not None:
267
+ c2ws = np.load(os.path.join(action_path, "poses.npy")) # opencv coordinate
268
+ len_c2ws = ((len(c2ws) - 1) // 4) * 4 + 1
269
+ frame_num = min(frame_num, len_c2ws)
270
+ c2ws = c2ws[:frame_num]
271
+
272
+ # preprocess
273
+ guide_scale = (guide_scale, guide_scale) if isinstance(
274
+ guide_scale, float) else guide_scale
275
+ img = TF.to_tensor(img).sub_(0.5).div_(0.5).to(self.device)
276
+
277
+ F = frame_num
278
+ h, w = img.shape[1:]
279
+ aspect_ratio = h / w
280
+ lat_h = round(
281
+ np.sqrt(max_area * aspect_ratio) // self.vae_stride[1] //
282
+ self.patch_size[1] * self.patch_size[1])
283
+ lat_w = round(
284
+ np.sqrt(max_area / aspect_ratio) // self.vae_stride[2] //
285
+ self.patch_size[2] * self.patch_size[2])
286
+ h = lat_h * self.vae_stride[1]
287
+ w = lat_w * self.vae_stride[2]
288
+ lat_f = (F - 1) // self.vae_stride[0] + 1
289
+ max_seq_len = lat_f * lat_h * lat_w // (
290
+ self.patch_size[1] * self.patch_size[2])
291
+ max_seq_len = int(math.ceil(max_seq_len / self.sp_size)) * self.sp_size
292
+
293
+ seed = seed if seed >= 0 else random.randint(0, sys.maxsize)
294
+ seed_g = torch.Generator(device=self.device)
295
+ seed_g.manual_seed(seed)
296
+ noise = torch.randn(
297
+ 16,
298
+ (F - 1) // self.vae_stride[0] + 1,
299
+ lat_h,
300
+ lat_w,
301
+ dtype=torch.float32,
302
+ generator=seed_g,
303
+ device=self.device)
304
+
305
+ msk = torch.ones(1, F, lat_h, lat_w, device=self.device)
306
+ msk[:, 1:] = 0
307
+ msk = torch.concat([
308
+ torch.repeat_interleave(msk[:, 0:1], repeats=4, dim=1), msk[:, 1:]
309
+ ],
310
+ dim=1)
311
+ msk = msk.view(1, msk.shape[1] // 4, 4, lat_h, lat_w)
312
+ msk = msk.transpose(1, 2)[0]
313
+
314
+ if n_prompt == "":
315
+ n_prompt = self.sample_neg_prompt
316
+
317
+ # preprocess
318
+ if not self.t5_cpu:
319
+ self.text_encoder.model.to(self.device)
320
+ context = self.text_encoder([input_prompt], self.device)
321
+ context_null = self.text_encoder([n_prompt], self.device)
322
+ if offload_model:
323
+ self.text_encoder.model.cpu()
324
+ else:
325
+ context = self.text_encoder([input_prompt], torch.device('cpu'))
326
+ context_null = self.text_encoder([n_prompt], torch.device('cpu'))
327
+ context = [t.to(self.device) for t in context]
328
+ context_null = [t.to(self.device) for t in context_null]
329
+
330
+ # cam preparation (only if action_path is provided)
331
+ dit_cond_dict = None
332
+ if action_path is not None:
333
+ Ks = torch.from_numpy(np.load(os.path.join(action_path, "intrinsics.npy"))).float()
334
+
335
+ # The provided intrinsics are for original image size (480p). We need to transform them according to the new image size (h, w).
336
+ Ks = get_Ks_transformed(Ks,
337
+ height_org=480,
338
+ width_org=832,
339
+ height_resize=h,
340
+ width_resize=w,
341
+ height_final=h,
342
+ width_final=w)
343
+ Ks = Ks[0]
344
+
345
+ len_c2ws = len(c2ws)
346
+ c2ws_infer = interpolate_camera_poses(
347
+ src_indices=np.linspace(0, len_c2ws - 1, len_c2ws),
348
+ src_rot_mat=c2ws[:, :3, :3],
349
+ src_trans_vec=c2ws[:, :3, 3],
350
+ tgt_indices=np.linspace(0, len_c2ws - 1, int((len_c2ws - 1) // 4) + 1),
351
+ )
352
+ c2ws_infer = compute_relative_poses(c2ws_infer, framewise=True)
353
+ Ks = Ks.repeat(len(c2ws_infer), 1)
354
+
355
+ c2ws_infer = c2ws_infer.to(self.device)
356
+ Ks = Ks.to(self.device)
357
+ c2ws_plucker_emb = get_plucker_embeddings(c2ws_infer, Ks, h, w)
358
+ c2ws_plucker_emb = rearrange(
359
+ c2ws_plucker_emb,
360
+ 'f (h c1) (w c2) c -> (f h w) (c c1 c2)',
361
+ c1=int(h // lat_h),
362
+ c2=int(w // lat_w),
363
+ )
364
+ c2ws_plucker_emb = c2ws_plucker_emb[None, ...] # [b, f*h*w, c]
365
+ c2ws_plucker_emb = rearrange(c2ws_plucker_emb, 'b (f h w) c -> b c f h w', f=lat_f, h=lat_h, w=lat_w).to(self.param_dtype)
366
+ dit_cond_dict = {
367
+ "c2ws_plucker_emb": c2ws_plucker_emb.chunk(1, dim=0),
368
+ }
369
+
370
+ y = self.vae.encode([
371
+ torch.concat([
372
+ torch.nn.functional.interpolate(
373
+ img[None].cpu(), size=(h, w), mode='bicubic').transpose(
374
+ 0, 1),
375
+ torch.zeros(3, F - 1, h, w)
376
+ ],
377
+ dim=1).to(self.device)
378
+ ])[0]
379
+ y = torch.concat([msk, y])
380
+
381
+ @contextmanager
382
+ def noop_no_sync():
383
+ yield
384
+
385
+ no_sync_low_noise = getattr(self.low_noise_model, 'no_sync',
386
+ noop_no_sync)
387
+ no_sync_high_noise = getattr(self.high_noise_model, 'no_sync',
388
+ noop_no_sync)
389
+
390
+ # evaluation mode
391
+ with (
392
+ torch.amp.autocast('cuda', dtype=self.param_dtype),
393
+ torch.no_grad(),
394
+ no_sync_low_noise(),
395
+ no_sync_high_noise(),
396
+ ):
397
+ boundary = self.boundary * self.num_train_timesteps
398
+
399
+ if sample_solver == 'unipc':
400
+ sample_scheduler = FlowUniPCMultistepScheduler(
401
+ num_train_timesteps=self.num_train_timesteps,
402
+ shift=1,
403
+ use_dynamic_shifting=False)
404
+ sample_scheduler.set_timesteps(
405
+ sampling_steps, device=self.device, shift=shift)
406
+ timesteps = sample_scheduler.timesteps
407
+ elif sample_solver == 'dpm++':
408
+ sample_scheduler = FlowDPMSolverMultistepScheduler(
409
+ num_train_timesteps=self.num_train_timesteps,
410
+ shift=1,
411
+ use_dynamic_shifting=False)
412
+ sampling_sigmas = get_sampling_sigmas(sampling_steps, shift)
413
+ timesteps, _ = retrieve_timesteps(
414
+ sample_scheduler,
415
+ device=self.device,
416
+ sigmas=sampling_sigmas)
417
+ else:
418
+ raise NotImplementedError("Unsupported solver.")
419
+
420
+ # sample videos
421
+ latent = noise
422
+
423
+ arg_c = {
424
+ 'context': [context[0]],
425
+ 'seq_len': max_seq_len,
426
+ 'y': [y],
427
+ 'dit_cond_dict': dit_cond_dict,
428
+ }
429
+
430
+ arg_null = {
431
+ 'context': context_null,
432
+ 'seq_len': max_seq_len,
433
+ 'y': [y],
434
+ 'dit_cond_dict': dit_cond_dict,
435
+ }
436
+
437
+ if offload_model:
438
+ torch.cuda.empty_cache()
439
+
440
+ for _, t in enumerate(tqdm(timesteps)):
441
+ latent_model_input = [latent.to(self.device)]
442
+ timestep = [t]
443
+
444
+ timestep = torch.stack(timestep).to(self.device)
445
+
446
+ model = self._prepare_model_for_timestep(
447
+ t, boundary, offload_model)
448
+ sample_guide_scale = guide_scale[1] if t.item(
449
+ ) >= boundary else guide_scale[0]
450
+
451
+ noise_pred_cond = model(
452
+ latent_model_input, t=timestep, **arg_c)[0]
453
+ if offload_model:
454
+ torch.cuda.empty_cache()
455
+ noise_pred_uncond = model(
456
+ latent_model_input, t=timestep, **arg_null)[0]
457
+ if offload_model:
458
+ torch.cuda.empty_cache()
459
+ noise_pred = noise_pred_uncond + sample_guide_scale * (
460
+ noise_pred_cond - noise_pred_uncond)
461
+
462
+ temp_x0 = sample_scheduler.step(
463
+ noise_pred.unsqueeze(0),
464
+ t,
465
+ latent.unsqueeze(0),
466
+ return_dict=False,
467
+ generator=seed_g)[0]
468
+ latent = temp_x0.squeeze(0)
469
+
470
+ x0 = [latent]
471
+ del latent_model_input, timestep
472
+
473
+ if offload_model:
474
+ self.low_noise_model.cpu()
475
+ self.high_noise_model.cpu()
476
+ torch.cuda.empty_cache()
477
+
478
+ if self.rank == 0:
479
+ videos = self.vae.decode(x0)
480
+
481
+ del noise, latent, x0
482
+ del sample_scheduler
483
+ if offload_model:
484
+ gc.collect()
485
+ torch.cuda.synchronize()
486
+ if dist.is_initialized():
487
+ dist.barrier()
488
+
489
+ return videos[0] if self.rank == 0 else None
wan/modules/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .attention import flash_attention
2
+ from .model import WanModel
3
+ from .t5 import T5Decoder, T5Encoder, T5EncoderModel, T5Model
4
+ from .tokenizers import HuggingfaceTokenizer
5
+ from .vae2_1 import Wan2_1_VAE
6
+ from .vae2_2 import Wan2_2_VAE
7
+
8
+ __all__ = [
9
+ 'Wan2_1_VAE',
10
+ 'Wan2_2_VAE',
11
+ 'WanModel',
12
+ 'T5Model',
13
+ 'T5Encoder',
14
+ 'T5Decoder',
15
+ 'T5EncoderModel',
16
+ 'HuggingfaceTokenizer',
17
+ 'flash_attention',
18
+ ]
wan/modules/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (557 Bytes). View file
 
wan/modules/__pycache__/attention.cpython-310.pyc ADDED
Binary file (3.97 kB). View file
 
wan/modules/__pycache__/model.cpython-310.pyc ADDED
Binary file (18.3 kB). View file
 
wan/modules/__pycache__/t5.cpython-310.pyc ADDED
Binary file (13 kB). View file
 
wan/modules/__pycache__/tokenizers.cpython-310.pyc ADDED
Binary file (2.58 kB). View file
 
wan/modules/__pycache__/vae2_1.cpython-310.pyc ADDED
Binary file (17 kB). View file
 
wan/modules/__pycache__/vae2_2.cpython-310.pyc ADDED
Binary file (22.1 kB). View file
 
wan/modules/animate/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+
2
+ from .model_animate import WanAnimateModel
3
+ from .clip import CLIPModel
4
+ __all__ = ['WanAnimateModel', 'CLIPModel']
wan/modules/animate/animate_utils.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import numbers
4
+ from peft import LoraConfig
5
+
6
+
7
+ def get_loraconfig(transformer, rank=128, alpha=128, init_lora_weights="gaussian"):
8
+ target_modules = []
9
+ for name, module in transformer.named_modules():
10
+ if "blocks" in name and "face" not in name and "modulation" not in name and isinstance(module, torch.nn.Linear):
11
+ target_modules.append(name)
12
+
13
+ transformer_lora_config = LoraConfig(
14
+ r=rank,
15
+ lora_alpha=alpha,
16
+ init_lora_weights=init_lora_weights,
17
+ target_modules=target_modules,
18
+ )
19
+ return transformer_lora_config
20
+
21
+
22
+
23
+ class TensorList(object):
24
+
25
+ def __init__(self, tensors):
26
+ """
27
+ tensors: a list of torch.Tensor objects. No need to have uniform shape.
28
+ """
29
+ assert isinstance(tensors, (list, tuple))
30
+ assert all(isinstance(u, torch.Tensor) for u in tensors)
31
+ assert len(set([u.ndim for u in tensors])) == 1
32
+ assert len(set([u.dtype for u in tensors])) == 1
33
+ assert len(set([u.device for u in tensors])) == 1
34
+ self.tensors = tensors
35
+
36
+ def to(self, *args, **kwargs):
37
+ return TensorList([u.to(*args, **kwargs) for u in self.tensors])
38
+
39
+ def size(self, dim):
40
+ assert dim == 0, 'only support get the 0th size'
41
+ return len(self.tensors)
42
+
43
+ def pow(self, *args, **kwargs):
44
+ return TensorList([u.pow(*args, **kwargs) for u in self.tensors])
45
+
46
+ def squeeze(self, dim):
47
+ assert dim != 0
48
+ if dim > 0:
49
+ dim -= 1
50
+ return TensorList([u.squeeze(dim) for u in self.tensors])
51
+
52
+ def type(self, *args, **kwargs):
53
+ return TensorList([u.type(*args, **kwargs) for u in self.tensors])
54
+
55
+ def type_as(self, other):
56
+ assert isinstance(other, (torch.Tensor, TensorList))
57
+ if isinstance(other, torch.Tensor):
58
+ return TensorList([u.type_as(other) for u in self.tensors])
59
+ else:
60
+ return TensorList([u.type(other.dtype) for u in self.tensors])
61
+
62
+ @property
63
+ def dtype(self):
64
+ return self.tensors[0].dtype
65
+
66
+ @property
67
+ def device(self):
68
+ return self.tensors[0].device
69
+
70
+ @property
71
+ def ndim(self):
72
+ return 1 + self.tensors[0].ndim
73
+
74
+ def __getitem__(self, index):
75
+ return self.tensors[index]
76
+
77
+ def __len__(self):
78
+ return len(self.tensors)
79
+
80
+ def __add__(self, other):
81
+ return self._apply(other, lambda u, v: u + v)
82
+
83
+ def __radd__(self, other):
84
+ return self._apply(other, lambda u, v: v + u)
85
+
86
+ def __sub__(self, other):
87
+ return self._apply(other, lambda u, v: u - v)
88
+
89
+ def __rsub__(self, other):
90
+ return self._apply(other, lambda u, v: v - u)
91
+
92
+ def __mul__(self, other):
93
+ return self._apply(other, lambda u, v: u * v)
94
+
95
+ def __rmul__(self, other):
96
+ return self._apply(other, lambda u, v: v * u)
97
+
98
+ def __floordiv__(self, other):
99
+ return self._apply(other, lambda u, v: u // v)
100
+
101
+ def __truediv__(self, other):
102
+ return self._apply(other, lambda u, v: u / v)
103
+
104
+ def __rfloordiv__(self, other):
105
+ return self._apply(other, lambda u, v: v // u)
106
+
107
+ def __rtruediv__(self, other):
108
+ return self._apply(other, lambda u, v: v / u)
109
+
110
+ def __pow__(self, other):
111
+ return self._apply(other, lambda u, v: u ** v)
112
+
113
+ def __rpow__(self, other):
114
+ return self._apply(other, lambda u, v: v ** u)
115
+
116
+ def __neg__(self):
117
+ return TensorList([-u for u in self.tensors])
118
+
119
+ def __iter__(self):
120
+ for tensor in self.tensors:
121
+ yield tensor
122
+
123
+ def __repr__(self):
124
+ return 'TensorList: \n' + repr(self.tensors)
125
+
126
+ def _apply(self, other, op):
127
+ if isinstance(other, (list, tuple, TensorList)) or (
128
+ isinstance(other, torch.Tensor) and (
129
+ other.numel() > 1 or other.ndim > 1
130
+ )
131
+ ):
132
+ assert len(other) == len(self.tensors)
133
+ return TensorList([op(u, v) for u, v in zip(self.tensors, other)])
134
+ elif isinstance(other, numbers.Number) or (
135
+ isinstance(other, torch.Tensor) and (
136
+ other.numel() == 1 and other.ndim <= 1
137
+ )
138
+ ):
139
+ return TensorList([op(u, other) for u in self.tensors])
140
+ else:
141
+ raise TypeError(
142
+ f'unsupported operand for *: "TensorList" and "{type(other)}"'
143
+ )
wan/modules/animate/clip.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from ``https://github.com/openai/CLIP'' and ``https://github.com/mlfoundations/open_clip''
2
+
3
+ import logging
4
+ import math
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ import torchvision.transforms as T
10
+
11
+ from ..attention import flash_attention
12
+ from ..tokenizers import HuggingfaceTokenizer
13
+ from .xlm_roberta import XLMRoberta
14
+
15
+ __all__ = [
16
+ 'XLMRobertaCLIP',
17
+ 'clip_xlm_roberta_vit_h_14',
18
+ 'CLIPModel',
19
+ ]
20
+
21
+
22
+ def pos_interpolate(pos, seq_len):
23
+ if pos.size(1) == seq_len:
24
+ return pos
25
+ else:
26
+ src_grid = int(math.sqrt(pos.size(1)))
27
+ tar_grid = int(math.sqrt(seq_len))
28
+ n = pos.size(1) - src_grid * src_grid
29
+ return torch.cat([
30
+ pos[:, :n],
31
+ F.interpolate(
32
+ pos[:, n:].float().reshape(1, src_grid, src_grid, -1).permute(
33
+ 0, 3, 1, 2),
34
+ size=(tar_grid, tar_grid),
35
+ mode='bicubic',
36
+ align_corners=False).flatten(2).transpose(1, 2)
37
+ ],
38
+ dim=1)
39
+
40
+
41
+ class QuickGELU(nn.Module):
42
+
43
+ def forward(self, x):
44
+ return x * torch.sigmoid(1.702 * x)
45
+
46
+
47
+ class LayerNorm(nn.LayerNorm):
48
+
49
+ def forward(self, x):
50
+ return super().forward(x.float()).type_as(x)
51
+
52
+
53
+ class SelfAttention(nn.Module):
54
+
55
+ def __init__(self,
56
+ dim,
57
+ num_heads,
58
+ causal=False,
59
+ attn_dropout=0.0,
60
+ proj_dropout=0.0):
61
+ assert dim % num_heads == 0
62
+ super().__init__()
63
+ self.dim = dim
64
+ self.num_heads = num_heads
65
+ self.head_dim = dim // num_heads
66
+ self.causal = causal
67
+ self.attn_dropout = attn_dropout
68
+ self.proj_dropout = proj_dropout
69
+
70
+ # layers
71
+ self.to_qkv = nn.Linear(dim, dim * 3)
72
+ self.proj = nn.Linear(dim, dim)
73
+
74
+ def forward(self, x):
75
+ """
76
+ x: [B, L, C].
77
+ """
78
+ b, s, c, n, d = *x.size(), self.num_heads, self.head_dim
79
+
80
+ # compute query, key, value
81
+ q, k, v = self.to_qkv(x).view(b, s, 3, n, d).unbind(2)
82
+
83
+ # compute attention
84
+ p = self.attn_dropout if self.training else 0.0
85
+ x = flash_attention(q, k, v, dropout_p=p, causal=self.causal, version=2)
86
+ x = x.reshape(b, s, c)
87
+
88
+ # output
89
+ x = self.proj(x)
90
+ x = F.dropout(x, self.proj_dropout, self.training)
91
+ return x
92
+
93
+
94
+ class SwiGLU(nn.Module):
95
+
96
+ def __init__(self, dim, mid_dim):
97
+ super().__init__()
98
+ self.dim = dim
99
+ self.mid_dim = mid_dim
100
+
101
+ # layers
102
+ self.fc1 = nn.Linear(dim, mid_dim)
103
+ self.fc2 = nn.Linear(dim, mid_dim)
104
+ self.fc3 = nn.Linear(mid_dim, dim)
105
+
106
+ def forward(self, x):
107
+ x = F.silu(self.fc1(x)) * self.fc2(x)
108
+ x = self.fc3(x)
109
+ return x
110
+
111
+
112
+ class AttentionBlock(nn.Module):
113
+
114
+ def __init__(self,
115
+ dim,
116
+ mlp_ratio,
117
+ num_heads,
118
+ post_norm=False,
119
+ causal=False,
120
+ activation='quick_gelu',
121
+ attn_dropout=0.0,
122
+ proj_dropout=0.0,
123
+ norm_eps=1e-5):
124
+ assert activation in ['quick_gelu', 'gelu', 'swi_glu']
125
+ super().__init__()
126
+ self.dim = dim
127
+ self.mlp_ratio = mlp_ratio
128
+ self.num_heads = num_heads
129
+ self.post_norm = post_norm
130
+ self.causal = causal
131
+ self.norm_eps = norm_eps
132
+
133
+ # layers
134
+ self.norm1 = LayerNorm(dim, eps=norm_eps)
135
+ self.attn = SelfAttention(dim, num_heads, causal, attn_dropout,
136
+ proj_dropout)
137
+ self.norm2 = LayerNorm(dim, eps=norm_eps)
138
+ if activation == 'swi_glu':
139
+ self.mlp = SwiGLU(dim, int(dim * mlp_ratio))
140
+ else:
141
+ self.mlp = nn.Sequential(
142
+ nn.Linear(dim, int(dim * mlp_ratio)),
143
+ QuickGELU() if activation == 'quick_gelu' else nn.GELU(),
144
+ nn.Linear(int(dim * mlp_ratio), dim), nn.Dropout(proj_dropout))
145
+
146
+ def forward(self, x):
147
+ if self.post_norm:
148
+ x = x + self.norm1(self.attn(x))
149
+ x = x + self.norm2(self.mlp(x))
150
+ else:
151
+ x = x + self.attn(self.norm1(x))
152
+ x = x + self.mlp(self.norm2(x))
153
+ return x
154
+
155
+
156
+ class AttentionPool(nn.Module):
157
+
158
+ def __init__(self,
159
+ dim,
160
+ mlp_ratio,
161
+ num_heads,
162
+ activation='gelu',
163
+ proj_dropout=0.0,
164
+ norm_eps=1e-5):
165
+ assert dim % num_heads == 0
166
+ super().__init__()
167
+ self.dim = dim
168
+ self.mlp_ratio = mlp_ratio
169
+ self.num_heads = num_heads
170
+ self.head_dim = dim // num_heads
171
+ self.proj_dropout = proj_dropout
172
+ self.norm_eps = norm_eps
173
+
174
+ # layers
175
+ gain = 1.0 / math.sqrt(dim)
176
+ self.cls_embedding = nn.Parameter(gain * torch.randn(1, 1, dim))
177
+ self.to_q = nn.Linear(dim, dim)
178
+ self.to_kv = nn.Linear(dim, dim * 2)
179
+ self.proj = nn.Linear(dim, dim)
180
+ self.norm = LayerNorm(dim, eps=norm_eps)
181
+ self.mlp = nn.Sequential(
182
+ nn.Linear(dim, int(dim * mlp_ratio)),
183
+ QuickGELU() if activation == 'quick_gelu' else nn.GELU(),
184
+ nn.Linear(int(dim * mlp_ratio), dim), nn.Dropout(proj_dropout))
185
+
186
+ def forward(self, x):
187
+ """
188
+ x: [B, L, C].
189
+ """
190
+ b, s, c, n, d = *x.size(), self.num_heads, self.head_dim
191
+
192
+ # compute query, key, value
193
+ q = self.to_q(self.cls_embedding).view(1, 1, n, d).expand(b, -1, -1, -1)
194
+ k, v = self.to_kv(x).view(b, s, 2, n, d).unbind(2)
195
+
196
+ # compute attention
197
+ x = flash_attention(q, k, v, version=2)
198
+ x = x.reshape(b, 1, c)
199
+
200
+ # output
201
+ x = self.proj(x)
202
+ x = F.dropout(x, self.proj_dropout, self.training)
203
+
204
+ # mlp
205
+ x = x + self.mlp(self.norm(x))
206
+ return x[:, 0]
207
+
208
+
209
+ class VisionTransformer(nn.Module):
210
+
211
+ def __init__(self,
212
+ image_size=224,
213
+ patch_size=16,
214
+ dim=768,
215
+ mlp_ratio=4,
216
+ out_dim=512,
217
+ num_heads=12,
218
+ num_layers=12,
219
+ pool_type='token',
220
+ pre_norm=True,
221
+ post_norm=False,
222
+ activation='quick_gelu',
223
+ attn_dropout=0.0,
224
+ proj_dropout=0.0,
225
+ embedding_dropout=0.0,
226
+ norm_eps=1e-5):
227
+ if image_size % patch_size != 0:
228
+ print(
229
+ '[WARNING] image_size is not divisible by patch_size',
230
+ flush=True)
231
+ assert pool_type in ('token', 'token_fc', 'attn_pool')
232
+ out_dim = out_dim or dim
233
+ super().__init__()
234
+ self.image_size = image_size
235
+ self.patch_size = patch_size
236
+ self.num_patches = (image_size // patch_size)**2
237
+ self.dim = dim
238
+ self.mlp_ratio = mlp_ratio
239
+ self.out_dim = out_dim
240
+ self.num_heads = num_heads
241
+ self.num_layers = num_layers
242
+ self.pool_type = pool_type
243
+ self.post_norm = post_norm
244
+ self.norm_eps = norm_eps
245
+
246
+ # embeddings
247
+ gain = 1.0 / math.sqrt(dim)
248
+ self.patch_embedding = nn.Conv2d(
249
+ 3,
250
+ dim,
251
+ kernel_size=patch_size,
252
+ stride=patch_size,
253
+ bias=not pre_norm)
254
+ if pool_type in ('token', 'token_fc'):
255
+ self.cls_embedding = nn.Parameter(gain * torch.randn(1, 1, dim))
256
+ self.pos_embedding = nn.Parameter(gain * torch.randn(
257
+ 1, self.num_patches +
258
+ (1 if pool_type in ('token', 'token_fc') else 0), dim))
259
+ self.dropout = nn.Dropout(embedding_dropout)
260
+
261
+ # transformer
262
+ self.pre_norm = LayerNorm(dim, eps=norm_eps) if pre_norm else None
263
+ self.transformer = nn.Sequential(*[
264
+ AttentionBlock(dim, mlp_ratio, num_heads, post_norm, False,
265
+ activation, attn_dropout, proj_dropout, norm_eps)
266
+ for _ in range(num_layers)
267
+ ])
268
+ self.post_norm = LayerNorm(dim, eps=norm_eps)
269
+
270
+ # head
271
+ if pool_type == 'token':
272
+ self.head = nn.Parameter(gain * torch.randn(dim, out_dim))
273
+ elif pool_type == 'token_fc':
274
+ self.head = nn.Linear(dim, out_dim)
275
+ elif pool_type == 'attn_pool':
276
+ self.head = AttentionPool(dim, mlp_ratio, num_heads, activation,
277
+ proj_dropout, norm_eps)
278
+
279
+ def forward(self, x, interpolation=False, use_31_block=False):
280
+ b = x.size(0)
281
+
282
+ # embeddings
283
+ x = self.patch_embedding(x).flatten(2).permute(0, 2, 1)
284
+ if self.pool_type in ('token', 'token_fc'):
285
+ x = torch.cat([self.cls_embedding.expand(b, -1, -1), x], dim=1)
286
+ if interpolation:
287
+ e = pos_interpolate(self.pos_embedding, x.size(1))
288
+ else:
289
+ e = self.pos_embedding
290
+ x = self.dropout(x + e)
291
+ if self.pre_norm is not None:
292
+ x = self.pre_norm(x)
293
+
294
+ # transformer
295
+ if use_31_block:
296
+ x = self.transformer[:-1](x)
297
+ return x
298
+ else:
299
+ x = self.transformer(x)
300
+ return x
301
+
302
+
303
+ class XLMRobertaWithHead(XLMRoberta):
304
+
305
+ def __init__(self, **kwargs):
306
+ self.out_dim = kwargs.pop('out_dim')
307
+ super().__init__(**kwargs)
308
+
309
+ # head
310
+ mid_dim = (self.dim + self.out_dim) // 2
311
+ self.head = nn.Sequential(
312
+ nn.Linear(self.dim, mid_dim, bias=False), nn.GELU(),
313
+ nn.Linear(mid_dim, self.out_dim, bias=False))
314
+
315
+ def forward(self, ids):
316
+ # xlm-roberta
317
+ x = super().forward(ids)
318
+
319
+ # average pooling
320
+ mask = ids.ne(self.pad_id).unsqueeze(-1).to(x)
321
+ x = (x * mask).sum(dim=1) / mask.sum(dim=1)
322
+
323
+ # head
324
+ x = self.head(x)
325
+ return x
326
+
327
+
328
+ class XLMRobertaCLIP(nn.Module):
329
+
330
+ def __init__(self,
331
+ embed_dim=1024,
332
+ image_size=224,
333
+ patch_size=14,
334
+ vision_dim=1280,
335
+ vision_mlp_ratio=4,
336
+ vision_heads=16,
337
+ vision_layers=32,
338
+ vision_pool='token',
339
+ vision_pre_norm=True,
340
+ vision_post_norm=False,
341
+ activation='gelu',
342
+ vocab_size=250002,
343
+ max_text_len=514,
344
+ type_size=1,
345
+ pad_id=1,
346
+ text_dim=1024,
347
+ text_heads=16,
348
+ text_layers=24,
349
+ text_post_norm=True,
350
+ text_dropout=0.1,
351
+ attn_dropout=0.0,
352
+ proj_dropout=0.0,
353
+ embedding_dropout=0.0,
354
+ norm_eps=1e-5):
355
+ super().__init__()
356
+ self.embed_dim = embed_dim
357
+ self.image_size = image_size
358
+ self.patch_size = patch_size
359
+ self.vision_dim = vision_dim
360
+ self.vision_mlp_ratio = vision_mlp_ratio
361
+ self.vision_heads = vision_heads
362
+ self.vision_layers = vision_layers
363
+ self.vision_pre_norm = vision_pre_norm
364
+ self.vision_post_norm = vision_post_norm
365
+ self.activation = activation
366
+ self.vocab_size = vocab_size
367
+ self.max_text_len = max_text_len
368
+ self.type_size = type_size
369
+ self.pad_id = pad_id
370
+ self.text_dim = text_dim
371
+ self.text_heads = text_heads
372
+ self.text_layers = text_layers
373
+ self.text_post_norm = text_post_norm
374
+ self.norm_eps = norm_eps
375
+
376
+ # models
377
+ self.visual = VisionTransformer(
378
+ image_size=image_size,
379
+ patch_size=patch_size,
380
+ dim=vision_dim,
381
+ mlp_ratio=vision_mlp_ratio,
382
+ out_dim=embed_dim,
383
+ num_heads=vision_heads,
384
+ num_layers=vision_layers,
385
+ pool_type=vision_pool,
386
+ pre_norm=vision_pre_norm,
387
+ post_norm=vision_post_norm,
388
+ activation=activation,
389
+ attn_dropout=attn_dropout,
390
+ proj_dropout=proj_dropout,
391
+ embedding_dropout=embedding_dropout,
392
+ norm_eps=norm_eps)
393
+ self.textual = XLMRobertaWithHead(
394
+ vocab_size=vocab_size,
395
+ max_seq_len=max_text_len,
396
+ type_size=type_size,
397
+ pad_id=pad_id,
398
+ dim=text_dim,
399
+ out_dim=embed_dim,
400
+ num_heads=text_heads,
401
+ num_layers=text_layers,
402
+ post_norm=text_post_norm,
403
+ dropout=text_dropout)
404
+ self.log_scale = nn.Parameter(math.log(1 / 0.07) * torch.ones([]))
405
+
406
+ def forward(self, imgs, txt_ids):
407
+ """
408
+ imgs: [B, 3, H, W] of torch.float32.
409
+ - mean: [0.48145466, 0.4578275, 0.40821073]
410
+ - std: [0.26862954, 0.26130258, 0.27577711]
411
+ txt_ids: [B, L] of torch.long.
412
+ Encoded by data.CLIPTokenizer.
413
+ """
414
+ xi = self.visual(imgs)
415
+ xt = self.textual(txt_ids)
416
+ return xi, xt
417
+
418
+ def param_groups(self):
419
+ groups = [{
420
+ 'params': [
421
+ p for n, p in self.named_parameters()
422
+ if 'norm' in n or n.endswith('bias')
423
+ ],
424
+ 'weight_decay': 0.0
425
+ }, {
426
+ 'params': [
427
+ p for n, p in self.named_parameters()
428
+ if not ('norm' in n or n.endswith('bias'))
429
+ ]
430
+ }]
431
+ return groups
432
+
433
+
434
+ def _clip(pretrained=False,
435
+ pretrained_name=None,
436
+ model_cls=XLMRobertaCLIP,
437
+ return_transforms=False,
438
+ return_tokenizer=False,
439
+ tokenizer_padding='eos',
440
+ dtype=torch.float32,
441
+ device='cpu',
442
+ **kwargs):
443
+ # init a model on device
444
+ with torch.device(device):
445
+ model = model_cls(**kwargs)
446
+
447
+ # set device
448
+ model = model.to(dtype=dtype, device=device)
449
+ output = (model,)
450
+
451
+ # init transforms
452
+ if return_transforms:
453
+ # mean and std
454
+ if 'siglip' in pretrained_name.lower():
455
+ mean, std = [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]
456
+ else:
457
+ mean = [0.48145466, 0.4578275, 0.40821073]
458
+ std = [0.26862954, 0.26130258, 0.27577711]
459
+
460
+ # transforms
461
+ transforms = T.Compose([
462
+ T.Resize((model.image_size, model.image_size),
463
+ interpolation=T.InterpolationMode.BICUBIC),
464
+ T.ToTensor(),
465
+ T.Normalize(mean=mean, std=std)
466
+ ])
467
+ output += (transforms,)
468
+ return output[0] if len(output) == 1 else output
469
+
470
+
471
+ def clip_xlm_roberta_vit_h_14(
472
+ pretrained=False,
473
+ pretrained_name='open-clip-xlm-roberta-large-vit-huge-14',
474
+ **kwargs):
475
+ cfg = dict(
476
+ embed_dim=1024,
477
+ image_size=224,
478
+ patch_size=14,
479
+ vision_dim=1280,
480
+ vision_mlp_ratio=4,
481
+ vision_heads=16,
482
+ vision_layers=32,
483
+ vision_pool='token',
484
+ activation='gelu',
485
+ vocab_size=250002,
486
+ max_text_len=514,
487
+ type_size=1,
488
+ pad_id=1,
489
+ text_dim=1024,
490
+ text_heads=16,
491
+ text_layers=24,
492
+ text_post_norm=True,
493
+ text_dropout=0.1,
494
+ attn_dropout=0.0,
495
+ proj_dropout=0.0,
496
+ embedding_dropout=0.0)
497
+ cfg.update(**kwargs)
498
+ return _clip(pretrained, pretrained_name, XLMRobertaCLIP, **cfg)
499
+
500
+
501
+ class CLIPModel:
502
+
503
+ def __init__(self, dtype, device, checkpoint_path, tokenizer_path):
504
+ self.dtype = dtype
505
+ self.device = device
506
+ self.checkpoint_path = checkpoint_path
507
+ self.tokenizer_path = tokenizer_path
508
+
509
+ # init model
510
+ self.model, self.transforms = clip_xlm_roberta_vit_h_14(
511
+ pretrained=False,
512
+ return_transforms=True,
513
+ return_tokenizer=False,
514
+ dtype=dtype,
515
+ device=device)
516
+ self.model = self.model.eval().requires_grad_(False)
517
+ logging.info(f'loading {checkpoint_path}')
518
+ self.model.load_state_dict(
519
+ torch.load(checkpoint_path, map_location='cpu'))
520
+
521
+ # init tokenizer
522
+ self.tokenizer = HuggingfaceTokenizer(
523
+ name=tokenizer_path,
524
+ seq_len=self.model.max_text_len - 2,
525
+ clean='whitespace')
526
+
527
+ def visual(self, videos):
528
+ # preprocess
529
+ size = (self.model.image_size,) * 2
530
+ videos = torch.cat([
531
+ F.interpolate(
532
+ u.transpose(0, 1),
533
+ size=size,
534
+ mode='bicubic',
535
+ align_corners=False) for u in videos
536
+ ])
537
+ videos = self.transforms.transforms[-1](videos.mul_(0.5).add_(0.5))
538
+
539
+ # forward
540
+ with torch.cuda.amp.autocast(dtype=self.dtype):
541
+ out = self.model.visual(videos, use_31_block=True)
542
+ return out
wan/modules/animate/face_blocks.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from torch import nn
3
+ import torch
4
+ from typing import Tuple, Optional
5
+ from einops import rearrange
6
+ import torch.nn.functional as F
7
+ import math
8
+ from ...distributed.util import gather_forward, get_rank, get_world_size
9
+
10
+
11
+ try:
12
+ from flash_attn import flash_attn_qkvpacked_func, flash_attn_func
13
+ except ImportError:
14
+ flash_attn_func = None
15
+
16
+ MEMORY_LAYOUT = {
17
+ "flash": (
18
+ lambda x: x.view(x.shape[0] * x.shape[1], *x.shape[2:]),
19
+ lambda x: x,
20
+ ),
21
+ "torch": (
22
+ lambda x: x.transpose(1, 2),
23
+ lambda x: x.transpose(1, 2),
24
+ ),
25
+ "vanilla": (
26
+ lambda x: x.transpose(1, 2),
27
+ lambda x: x.transpose(1, 2),
28
+ ),
29
+ }
30
+
31
+
32
+ def attention(
33
+ q,
34
+ k,
35
+ v,
36
+ mode="flash",
37
+ drop_rate=0,
38
+ attn_mask=None,
39
+ causal=False,
40
+ max_seqlen_q=None,
41
+ batch_size=1,
42
+ ):
43
+ """
44
+ Perform QKV self attention.
45
+
46
+ Args:
47
+ q (torch.Tensor): Query tensor with shape [b, s, a, d], where a is the number of heads.
48
+ k (torch.Tensor): Key tensor with shape [b, s1, a, d]
49
+ v (torch.Tensor): Value tensor with shape [b, s1, a, d]
50
+ mode (str): Attention mode. Choose from 'self_flash', 'cross_flash', 'torch', and 'vanilla'.
51
+ drop_rate (float): Dropout rate in attention map. (default: 0)
52
+ attn_mask (torch.Tensor): Attention mask with shape [b, s1] (cross_attn), or [b, a, s, s1] (torch or vanilla).
53
+ (default: None)
54
+ causal (bool): Whether to use causal attention. (default: False)
55
+ cu_seqlens_q (torch.Tensor): dtype torch.int32. The cumulative sequence lengths of the sequences in the batch,
56
+ used to index into q.
57
+ cu_seqlens_kv (torch.Tensor): dtype torch.int32. The cumulative sequence lengths of the sequences in the batch,
58
+ used to index into kv.
59
+ max_seqlen_q (int): The maximum sequence length in the batch of q.
60
+ max_seqlen_kv (int): The maximum sequence length in the batch of k and v.
61
+
62
+ Returns:
63
+ torch.Tensor: Output tensor after self attention with shape [b, s, ad]
64
+ """
65
+ pre_attn_layout, post_attn_layout = MEMORY_LAYOUT[mode]
66
+
67
+ if mode == "torch":
68
+ if attn_mask is not None and attn_mask.dtype != torch.bool:
69
+ attn_mask = attn_mask.to(q.dtype)
70
+ x = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask, dropout_p=drop_rate, is_causal=causal)
71
+
72
+ elif mode == "flash":
73
+ x = flash_attn_func(
74
+ q,
75
+ k,
76
+ v,
77
+ )
78
+ x = x.view(batch_size, max_seqlen_q, x.shape[-2], x.shape[-1]) # reshape x to [b, s, a, d]
79
+ elif mode == "vanilla":
80
+ scale_factor = 1 / math.sqrt(q.size(-1))
81
+
82
+ b, a, s, _ = q.shape
83
+ s1 = k.size(2)
84
+ attn_bias = torch.zeros(b, a, s, s1, dtype=q.dtype, device=q.device)
85
+ if causal:
86
+ # Only applied to self attention
87
+ assert attn_mask is None, "Causal mask and attn_mask cannot be used together"
88
+ temp_mask = torch.ones(b, a, s, s, dtype=torch.bool, device=q.device).tril(diagonal=0)
89
+ attn_bias.masked_fill_(temp_mask.logical_not(), float("-inf"))
90
+ attn_bias.to(q.dtype)
91
+
92
+ if attn_mask is not None:
93
+ if attn_mask.dtype == torch.bool:
94
+ attn_bias.masked_fill_(attn_mask.logical_not(), float("-inf"))
95
+ else:
96
+ attn_bias += attn_mask
97
+
98
+ attn = (q @ k.transpose(-2, -1)) * scale_factor
99
+ attn += attn_bias
100
+ attn = attn.softmax(dim=-1)
101
+ attn = torch.dropout(attn, p=drop_rate, train=True)
102
+ x = attn @ v
103
+ else:
104
+ raise NotImplementedError(f"Unsupported attention mode: {mode}")
105
+
106
+ x = post_attn_layout(x)
107
+ b, s, a, d = x.shape
108
+ out = x.reshape(b, s, -1)
109
+ return out
110
+
111
+
112
+ class CausalConv1d(nn.Module):
113
+
114
+ def __init__(self, chan_in, chan_out, kernel_size=3, stride=1, dilation=1, pad_mode="replicate", **kwargs):
115
+ super().__init__()
116
+
117
+ self.pad_mode = pad_mode
118
+ padding = (kernel_size - 1, 0) # T
119
+ self.time_causal_padding = padding
120
+
121
+ self.conv = nn.Conv1d(chan_in, chan_out, kernel_size, stride=stride, dilation=dilation, **kwargs)
122
+
123
+ def forward(self, x):
124
+ x = F.pad(x, self.time_causal_padding, mode=self.pad_mode)
125
+ return self.conv(x)
126
+
127
+
128
+
129
+ class FaceEncoder(nn.Module):
130
+ def __init__(self, in_dim: int, hidden_dim: int, num_heads=int, dtype=None, device=None):
131
+ factory_kwargs = {"dtype": dtype, "device": device}
132
+ super().__init__()
133
+
134
+ self.num_heads = num_heads
135
+ self.conv1_local = CausalConv1d(in_dim, 1024 * num_heads, 3, stride=1)
136
+ self.norm1 = nn.LayerNorm(hidden_dim // 8, elementwise_affine=False, eps=1e-6, **factory_kwargs)
137
+ self.act = nn.SiLU()
138
+ self.conv2 = CausalConv1d(1024, 1024, 3, stride=2)
139
+ self.conv3 = CausalConv1d(1024, 1024, 3, stride=2)
140
+
141
+ self.out_proj = nn.Linear(1024, hidden_dim)
142
+ self.norm1 = nn.LayerNorm(1024, elementwise_affine=False, eps=1e-6, **factory_kwargs)
143
+
144
+ self.norm2 = nn.LayerNorm(1024, elementwise_affine=False, eps=1e-6, **factory_kwargs)
145
+
146
+ self.norm3 = nn.LayerNorm(1024, elementwise_affine=False, eps=1e-6, **factory_kwargs)
147
+
148
+ self.padding_tokens = nn.Parameter(torch.zeros(1, 1, 1, hidden_dim))
149
+
150
+ def forward(self, x):
151
+
152
+ x = rearrange(x, "b t c -> b c t")
153
+ b, c, t = x.shape
154
+
155
+ x = self.conv1_local(x)
156
+ x = rearrange(x, "b (n c) t -> (b n) t c", n=self.num_heads)
157
+
158
+ x = self.norm1(x)
159
+ x = self.act(x)
160
+ x = rearrange(x, "b t c -> b c t")
161
+ x = self.conv2(x)
162
+ x = rearrange(x, "b c t -> b t c")
163
+ x = self.norm2(x)
164
+ x = self.act(x)
165
+ x = rearrange(x, "b t c -> b c t")
166
+ x = self.conv3(x)
167
+ x = rearrange(x, "b c t -> b t c")
168
+ x = self.norm3(x)
169
+ x = self.act(x)
170
+ x = self.out_proj(x)
171
+ x = rearrange(x, "(b n) t c -> b t n c", b=b)
172
+ padding = self.padding_tokens.repeat(b, x.shape[1], 1, 1)
173
+ x = torch.cat([x, padding], dim=-2)
174
+ x_local = x.clone()
175
+
176
+ return x_local
177
+
178
+
179
+
180
+ class RMSNorm(nn.Module):
181
+ def __init__(
182
+ self,
183
+ dim: int,
184
+ elementwise_affine=True,
185
+ eps: float = 1e-6,
186
+ device=None,
187
+ dtype=None,
188
+ ):
189
+ """
190
+ Initialize the RMSNorm normalization layer.
191
+
192
+ Args:
193
+ dim (int): The dimension of the input tensor.
194
+ eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6.
195
+
196
+ Attributes:
197
+ eps (float): A small value added to the denominator for numerical stability.
198
+ weight (nn.Parameter): Learnable scaling parameter.
199
+
200
+ """
201
+ factory_kwargs = {"device": device, "dtype": dtype}
202
+ super().__init__()
203
+ self.eps = eps
204
+ if elementwise_affine:
205
+ self.weight = nn.Parameter(torch.ones(dim, **factory_kwargs))
206
+
207
+ def _norm(self, x):
208
+ """
209
+ Apply the RMSNorm normalization to the input tensor.
210
+
211
+ Args:
212
+ x (torch.Tensor): The input tensor.
213
+
214
+ Returns:
215
+ torch.Tensor: The normalized tensor.
216
+
217
+ """
218
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
219
+
220
+ def forward(self, x):
221
+ """
222
+ Forward pass through the RMSNorm layer.
223
+
224
+ Args:
225
+ x (torch.Tensor): The input tensor.
226
+
227
+ Returns:
228
+ torch.Tensor: The output tensor after applying RMSNorm.
229
+
230
+ """
231
+ output = self._norm(x.float()).type_as(x)
232
+ if hasattr(self, "weight"):
233
+ output = output * self.weight
234
+ return output
235
+
236
+
237
+ def get_norm_layer(norm_layer):
238
+ """
239
+ Get the normalization layer.
240
+
241
+ Args:
242
+ norm_layer (str): The type of normalization layer.
243
+
244
+ Returns:
245
+ norm_layer (nn.Module): The normalization layer.
246
+ """
247
+ if norm_layer == "layer":
248
+ return nn.LayerNorm
249
+ elif norm_layer == "rms":
250
+ return RMSNorm
251
+ else:
252
+ raise NotImplementedError(f"Norm layer {norm_layer} is not implemented")
253
+
254
+
255
+ class FaceAdapter(nn.Module):
256
+ def __init__(
257
+ self,
258
+ hidden_dim: int,
259
+ heads_num: int,
260
+ qk_norm: bool = True,
261
+ qk_norm_type: str = "rms",
262
+ num_adapter_layers: int = 1,
263
+ dtype=None,
264
+ device=None,
265
+ ):
266
+
267
+ factory_kwargs = {"dtype": dtype, "device": device}
268
+ super().__init__()
269
+ self.hidden_size = hidden_dim
270
+ self.heads_num = heads_num
271
+ self.fuser_blocks = nn.ModuleList(
272
+ [
273
+ FaceBlock(
274
+ self.hidden_size,
275
+ self.heads_num,
276
+ qk_norm=qk_norm,
277
+ qk_norm_type=qk_norm_type,
278
+ **factory_kwargs,
279
+ )
280
+ for _ in range(num_adapter_layers)
281
+ ]
282
+ )
283
+
284
+ def forward(
285
+ self,
286
+ x: torch.Tensor,
287
+ motion_embed: torch.Tensor,
288
+ idx: int,
289
+ freqs_cis_q: Tuple[torch.Tensor, torch.Tensor] = None,
290
+ freqs_cis_k: Tuple[torch.Tensor, torch.Tensor] = None,
291
+ ) -> torch.Tensor:
292
+
293
+ return self.fuser_blocks[idx](x, motion_embed, freqs_cis_q, freqs_cis_k)
294
+
295
+
296
+
297
+ class FaceBlock(nn.Module):
298
+ def __init__(
299
+ self,
300
+ hidden_size: int,
301
+ heads_num: int,
302
+ qk_norm: bool = True,
303
+ qk_norm_type: str = "rms",
304
+ qk_scale: float = None,
305
+ dtype: Optional[torch.dtype] = None,
306
+ device: Optional[torch.device] = None,
307
+ ):
308
+ factory_kwargs = {"device": device, "dtype": dtype}
309
+ super().__init__()
310
+
311
+ self.deterministic = False
312
+ self.hidden_size = hidden_size
313
+ self.heads_num = heads_num
314
+ head_dim = hidden_size // heads_num
315
+ self.scale = qk_scale or head_dim**-0.5
316
+
317
+ self.linear1_kv = nn.Linear(hidden_size, hidden_size * 2, **factory_kwargs)
318
+ self.linear1_q = nn.Linear(hidden_size, hidden_size, **factory_kwargs)
319
+
320
+ self.linear2 = nn.Linear(hidden_size, hidden_size, **factory_kwargs)
321
+
322
+ qk_norm_layer = get_norm_layer(qk_norm_type)
323
+ self.q_norm = (
324
+ qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs) if qk_norm else nn.Identity()
325
+ )
326
+ self.k_norm = (
327
+ qk_norm_layer(head_dim, elementwise_affine=True, eps=1e-6, **factory_kwargs) if qk_norm else nn.Identity()
328
+ )
329
+
330
+ self.pre_norm_feat = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, **factory_kwargs)
331
+
332
+ self.pre_norm_motion = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, **factory_kwargs)
333
+
334
+ def forward(
335
+ self,
336
+ x: torch.Tensor,
337
+ motion_vec: torch.Tensor,
338
+ motion_mask: Optional[torch.Tensor] = None,
339
+ use_context_parallel=False,
340
+ ) -> torch.Tensor:
341
+
342
+ B, T, N, C = motion_vec.shape
343
+ T_comp = T
344
+
345
+ x_motion = self.pre_norm_motion(motion_vec)
346
+ x_feat = self.pre_norm_feat(x)
347
+
348
+ kv = self.linear1_kv(x_motion)
349
+ q = self.linear1_q(x_feat)
350
+
351
+ k, v = rearrange(kv, "B L N (K H D) -> K B L N H D", K=2, H=self.heads_num)
352
+ q = rearrange(q, "B S (H D) -> B S H D", H=self.heads_num)
353
+
354
+ # Apply QK-Norm if needed.
355
+ q = self.q_norm(q).to(v)
356
+ k = self.k_norm(k).to(v)
357
+
358
+ k = rearrange(k, "B L N H D -> (B L) N H D")
359
+ v = rearrange(v, "B L N H D -> (B L) N H D")
360
+
361
+ if use_context_parallel:
362
+ q = gather_forward(q, dim=1)
363
+
364
+ q = rearrange(q, "B (L S) H D -> (B L) S H D", L=T_comp)
365
+ # Compute attention.
366
+ attn = attention(
367
+ q,
368
+ k,
369
+ v,
370
+ max_seqlen_q=q.shape[1],
371
+ batch_size=q.shape[0],
372
+ )
373
+
374
+ attn = rearrange(attn, "(B L) S C -> B (L S) C", L=T_comp)
375
+ if use_context_parallel:
376
+ attn = torch.chunk(attn, get_world_size(), dim=1)[get_rank()]
377
+
378
+ output = self.linear2(attn)
379
+
380
+ if motion_mask is not None:
381
+ output = output * rearrange(motion_mask, "B T H W -> B (T H W)").unsqueeze(-1)
382
+
383
+ return output
wan/modules/animate/model_animate.py ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import math
3
+ import types
4
+ from copy import deepcopy
5
+ from einops import rearrange
6
+ from typing import List
7
+ import numpy as np
8
+ import torch
9
+ import torch.cuda.amp as amp
10
+ import torch.nn as nn
11
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
12
+ from diffusers.models.modeling_utils import ModelMixin
13
+ from diffusers.loaders import PeftAdapterMixin
14
+
15
+ from ...distributed.sequence_parallel import (
16
+ distributed_attention,
17
+ gather_forward,
18
+ get_rank,
19
+ get_world_size,
20
+ )
21
+
22
+
23
+ from ..model import (
24
+ Head,
25
+ WanAttentionBlock,
26
+ WanLayerNorm,
27
+ WanRMSNorm,
28
+ WanModel,
29
+ WanSelfAttention,
30
+ flash_attention,
31
+ rope_params,
32
+ sinusoidal_embedding_1d,
33
+ rope_apply
34
+ )
35
+
36
+ from .face_blocks import FaceEncoder, FaceAdapter
37
+ from .motion_encoder import Generator
38
+
39
+ class HeadAnimate(Head):
40
+
41
+ def forward(self, x, e):
42
+ """
43
+ Args:
44
+ x(Tensor): Shape [B, L1, C]
45
+ e(Tensor): Shape [B, L1, C]
46
+ """
47
+ assert e.dtype == torch.float32
48
+ with amp.autocast(dtype=torch.float32):
49
+ e = (self.modulation + e.unsqueeze(1)).chunk(2, dim=1)
50
+ x = (self.head(self.norm(x) * (1 + e[1]) + e[0]))
51
+ return x
52
+
53
+
54
+ class WanAnimateSelfAttention(WanSelfAttention):
55
+
56
+ def forward(self, x, seq_lens, grid_sizes, freqs):
57
+ """
58
+ Args:
59
+ x(Tensor): Shape [B, L, num_heads, C / num_heads]
60
+ seq_lens(Tensor): Shape [B]
61
+ grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W)
62
+ freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2]
63
+ """
64
+ b, s, n, d = *x.shape[:2], self.num_heads, self.head_dim
65
+
66
+ # query, key, value function
67
+ def qkv_fn(x):
68
+ q = self.norm_q(self.q(x)).view(b, s, n, d)
69
+ k = self.norm_k(self.k(x)).view(b, s, n, d)
70
+ v = self.v(x).view(b, s, n, d)
71
+ return q, k, v
72
+
73
+ q, k, v = qkv_fn(x)
74
+
75
+ x = flash_attention(
76
+ q=rope_apply(q, grid_sizes, freqs),
77
+ k=rope_apply(k, grid_sizes, freqs),
78
+ v=v,
79
+ k_lens=seq_lens,
80
+ window_size=self.window_size)
81
+
82
+ # output
83
+ x = x.flatten(2)
84
+ x = self.o(x)
85
+ return x
86
+
87
+
88
+ class WanAnimateCrossAttention(WanSelfAttention):
89
+ def __init__(
90
+ self,
91
+ dim,
92
+ num_heads,
93
+ window_size=(-1, -1),
94
+ qk_norm=True,
95
+ eps=1e-6,
96
+ use_img_emb=True
97
+ ):
98
+ super().__init__(
99
+ dim,
100
+ num_heads,
101
+ window_size,
102
+ qk_norm,
103
+ eps
104
+ )
105
+ self.use_img_emb = use_img_emb
106
+
107
+ if use_img_emb:
108
+ self.k_img = nn.Linear(dim, dim)
109
+ self.v_img = nn.Linear(dim, dim)
110
+ self.norm_k_img = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
111
+
112
+ def forward(self, x, context, context_lens):
113
+ """
114
+ x: [B, L1, C].
115
+ context: [B, L2, C].
116
+ context_lens: [B].
117
+ """
118
+ if self.use_img_emb:
119
+ context_img = context[:, :257]
120
+ context = context[:, 257:]
121
+ else:
122
+ context = context
123
+
124
+ b, n, d = x.size(0), self.num_heads, self.head_dim
125
+
126
+ # compute query, key, value
127
+ q = self.norm_q(self.q(x)).view(b, -1, n, d)
128
+ k = self.norm_k(self.k(context)).view(b, -1, n, d)
129
+ v = self.v(context).view(b, -1, n, d)
130
+
131
+ if self.use_img_emb:
132
+ k_img = self.norm_k_img(self.k_img(context_img)).view(b, -1, n, d)
133
+ v_img = self.v_img(context_img).view(b, -1, n, d)
134
+ img_x = flash_attention(q, k_img, v_img, k_lens=None)
135
+ # compute attention
136
+ x = flash_attention(q, k, v, k_lens=context_lens)
137
+
138
+ # output
139
+ x = x.flatten(2)
140
+
141
+ if self.use_img_emb:
142
+ img_x = img_x.flatten(2)
143
+ x = x + img_x
144
+
145
+ x = self.o(x)
146
+ return x
147
+
148
+
149
+ class WanAnimateAttentionBlock(nn.Module):
150
+ def __init__(self,
151
+ dim,
152
+ ffn_dim,
153
+ num_heads,
154
+ window_size=(-1, -1),
155
+ qk_norm=True,
156
+ cross_attn_norm=True,
157
+ eps=1e-6,
158
+ use_img_emb=True):
159
+
160
+ super().__init__()
161
+ self.dim = dim
162
+ self.ffn_dim = ffn_dim
163
+ self.num_heads = num_heads
164
+ self.window_size = window_size
165
+ self.qk_norm = qk_norm
166
+ self.cross_attn_norm = cross_attn_norm
167
+ self.eps = eps
168
+
169
+ # layers
170
+ self.norm1 = WanLayerNorm(dim, eps)
171
+ self.self_attn = WanAnimateSelfAttention(dim, num_heads, window_size, qk_norm, eps)
172
+
173
+ self.norm3 = WanLayerNorm(
174
+ dim, eps, elementwise_affine=True
175
+ ) if cross_attn_norm else nn.Identity()
176
+
177
+ self.cross_attn = WanAnimateCrossAttention(dim, num_heads, (-1, -1), qk_norm, eps, use_img_emb=use_img_emb)
178
+ self.norm2 = WanLayerNorm(dim, eps)
179
+ self.ffn = nn.Sequential(
180
+ nn.Linear(dim, ffn_dim),
181
+ nn.GELU(approximate='tanh'),
182
+ nn.Linear(ffn_dim, dim)
183
+ )
184
+
185
+ # modulation
186
+ self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim ** 0.5)
187
+
188
+ def forward(
189
+ self,
190
+ x,
191
+ e,
192
+ seq_lens,
193
+ grid_sizes,
194
+ freqs,
195
+ context,
196
+ context_lens,
197
+ ):
198
+ """
199
+ Args:
200
+ x(Tensor): Shape [B, L, C]
201
+ e(Tensor): Shape [B, L1, 6, C]
202
+ seq_lens(Tensor): Shape [B], length of each sequence in batch
203
+ grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W)
204
+ freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2]
205
+ """
206
+ assert e.dtype == torch.float32
207
+ with amp.autocast(dtype=torch.float32):
208
+ e = (self.modulation + e).chunk(6, dim=1)
209
+ assert e[0].dtype == torch.float32
210
+
211
+ # self-attention
212
+ y = self.self_attn(
213
+ self.norm1(x).float() * (1 + e[1]) + e[0], seq_lens, grid_sizes, freqs
214
+ )
215
+ with amp.autocast(dtype=torch.float32):
216
+ x = x + y * e[2]
217
+
218
+ # cross-attention & ffn function
219
+ def cross_attn_ffn(x, context, context_lens, e):
220
+ x = x + self.cross_attn(self.norm3(x), context, context_lens)
221
+ y = self.ffn(self.norm2(x).float() * (1 + e[4]) + e[3])
222
+ with amp.autocast(dtype=torch.float32):
223
+ x = x + y * e[5]
224
+ return x
225
+
226
+ x = cross_attn_ffn(x, context, context_lens, e)
227
+ return x
228
+
229
+
230
+ class MLPProj(torch.nn.Module):
231
+ def __init__(self, in_dim, out_dim):
232
+ super().__init__()
233
+
234
+ self.proj = torch.nn.Sequential(
235
+ torch.nn.LayerNorm(in_dim),
236
+ torch.nn.Linear(in_dim, in_dim),
237
+ torch.nn.GELU(),
238
+ torch.nn.Linear(in_dim, out_dim),
239
+ torch.nn.LayerNorm(out_dim),
240
+ )
241
+
242
+ def forward(self, image_embeds):
243
+ clip_extra_context_tokens = self.proj(image_embeds)
244
+ return clip_extra_context_tokens
245
+
246
+ class WanAnimateModel(ModelMixin, ConfigMixin, PeftAdapterMixin):
247
+ _no_split_modules = ['WanAttentionBlock']
248
+
249
+ @register_to_config
250
+ def __init__(self,
251
+ patch_size=(1, 2, 2),
252
+ text_len=512,
253
+ in_dim=36,
254
+ dim=5120,
255
+ ffn_dim=13824,
256
+ freq_dim=256,
257
+ text_dim=4096,
258
+ out_dim=16,
259
+ num_heads=40,
260
+ num_layers=40,
261
+ window_size=(-1, -1),
262
+ qk_norm=True,
263
+ cross_attn_norm=True,
264
+ eps=1e-6,
265
+ motion_encoder_dim=512,
266
+ use_context_parallel=False,
267
+ use_img_emb=True):
268
+
269
+ super().__init__()
270
+ self.patch_size = patch_size
271
+ self.text_len = text_len
272
+ self.in_dim = in_dim
273
+ self.dim = dim
274
+ self.ffn_dim = ffn_dim
275
+ self.freq_dim = freq_dim
276
+ self.text_dim = text_dim
277
+ self.out_dim = out_dim
278
+ self.num_heads = num_heads
279
+ self.num_layers = num_layers
280
+ self.window_size = window_size
281
+ self.qk_norm = qk_norm
282
+ self.cross_attn_norm = cross_attn_norm
283
+ self.eps = eps
284
+ self.motion_encoder_dim = motion_encoder_dim
285
+ self.use_context_parallel = use_context_parallel
286
+ self.use_img_emb = use_img_emb
287
+
288
+ # embeddings
289
+ self.patch_embedding = nn.Conv3d(
290
+ in_dim, dim, kernel_size=patch_size, stride=patch_size)
291
+
292
+ self.pose_patch_embedding = nn.Conv3d(
293
+ 16, dim, kernel_size=patch_size, stride=patch_size
294
+ )
295
+
296
+ self.text_embedding = nn.Sequential(
297
+ nn.Linear(text_dim, dim), nn.GELU(approximate='tanh'),
298
+ nn.Linear(dim, dim))
299
+
300
+ self.time_embedding = nn.Sequential(
301
+ nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
302
+ self.time_projection = nn.Sequential(nn.SiLU(), nn.Linear(dim, dim * 6))
303
+
304
+ # blocks
305
+ self.blocks = nn.ModuleList([
306
+ WanAnimateAttentionBlock(dim, ffn_dim, num_heads, window_size, qk_norm,
307
+ cross_attn_norm, eps, use_img_emb) for _ in range(num_layers)
308
+ ])
309
+
310
+ # head
311
+ self.head = HeadAnimate(dim, out_dim, patch_size, eps)
312
+
313
+ # buffers (don't use register_buffer otherwise dtype will be changed in to())
314
+ assert (dim % num_heads) == 0 and (dim // num_heads) % 2 == 0
315
+ d = dim // num_heads
316
+ self.freqs = torch.cat([
317
+ rope_params(1024, d - 4 * (d // 6)),
318
+ rope_params(1024, 2 * (d // 6)),
319
+ rope_params(1024, 2 * (d // 6))
320
+ ], dim=1)
321
+
322
+ self.img_emb = MLPProj(1280, dim)
323
+
324
+ # initialize weights
325
+ self.init_weights()
326
+
327
+ self.motion_encoder = Generator(size=512, style_dim=512, motion_dim=20)
328
+ self.face_adapter = FaceAdapter(
329
+ heads_num=self.num_heads,
330
+ hidden_dim=self.dim,
331
+ num_adapter_layers=self.num_layers // 5,
332
+ )
333
+
334
+ self.face_encoder = FaceEncoder(
335
+ in_dim=motion_encoder_dim,
336
+ hidden_dim=self.dim,
337
+ num_heads=4,
338
+ )
339
+
340
+ def after_patch_embedding(self, x: List[torch.Tensor], pose_latents, face_pixel_values):
341
+ pose_latents = [self.pose_patch_embedding(u.unsqueeze(0)) for u in pose_latents]
342
+ for x_, pose_latents_ in zip(x, pose_latents):
343
+ x_[:, :, 1:] += pose_latents_
344
+
345
+ b,c,T,h,w = face_pixel_values.shape
346
+ face_pixel_values = rearrange(face_pixel_values, "b c t h w -> (b t) c h w")
347
+
348
+ encode_bs = 8
349
+ face_pixel_values_tmp = []
350
+ for i in range(math.ceil(face_pixel_values.shape[0]/encode_bs)):
351
+ face_pixel_values_tmp.append(self.motion_encoder.get_motion(face_pixel_values[i*encode_bs:(i+1)*encode_bs]))
352
+
353
+ motion_vec = torch.cat(face_pixel_values_tmp)
354
+
355
+ motion_vec = rearrange(motion_vec, "(b t) c -> b t c", t=T)
356
+ motion_vec = self.face_encoder(motion_vec)
357
+
358
+ B, L, H, C = motion_vec.shape
359
+ pad_face = torch.zeros(B, 1, H, C).type_as(motion_vec)
360
+ motion_vec = torch.cat([pad_face, motion_vec], dim=1)
361
+ return x, motion_vec
362
+
363
+
364
+ def after_transformer_block(self, block_idx, x, motion_vec, motion_masks=None):
365
+ if block_idx % 5 == 0:
366
+ adapter_args = [x, motion_vec, motion_masks, self.use_context_parallel]
367
+ residual_out = self.face_adapter.fuser_blocks[block_idx // 5](*adapter_args)
368
+ x = residual_out + x
369
+ return x
370
+
371
+
372
+ def forward(
373
+ self,
374
+ x,
375
+ t,
376
+ clip_fea,
377
+ context,
378
+ seq_len,
379
+ y=None,
380
+ pose_latents=None,
381
+ face_pixel_values=None
382
+ ):
383
+ # params
384
+ device = self.patch_embedding.weight.device
385
+ if self.freqs.device != device:
386
+ self.freqs = self.freqs.to(device)
387
+
388
+ if y is not None:
389
+ x = [torch.cat([u, v], dim=0) for u, v in zip(x, y)]
390
+
391
+ # embeddings
392
+ x = [self.patch_embedding(u.unsqueeze(0)) for u in x]
393
+ x, motion_vec = self.after_patch_embedding(x, pose_latents, face_pixel_values)
394
+
395
+ grid_sizes = torch.stack(
396
+ [torch.tensor(u.shape[2:], dtype=torch.long) for u in x])
397
+ x = [u.flatten(2).transpose(1, 2) for u in x]
398
+ seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long)
399
+ assert seq_lens.max() <= seq_len
400
+ x = torch.cat([
401
+ torch.cat([u, u.new_zeros(1, seq_len - u.size(1), u.size(2))],
402
+ dim=1) for u in x
403
+ ])
404
+
405
+ # time embeddings
406
+ with amp.autocast(dtype=torch.float32):
407
+ e = self.time_embedding(
408
+ sinusoidal_embedding_1d(self.freq_dim, t).float()
409
+ )
410
+ e0 = self.time_projection(e).unflatten(1, (6, self.dim))
411
+ assert e.dtype == torch.float32 and e0.dtype == torch.float32
412
+
413
+ # context
414
+ context_lens = None
415
+ context = self.text_embedding(
416
+ torch.stack([
417
+ torch.cat(
418
+ [u, u.new_zeros(self.text_len - u.size(0), u.size(1))])
419
+ for u in context
420
+ ]))
421
+
422
+ if self.use_img_emb:
423
+ context_clip = self.img_emb(clip_fea) # bs x 257 x dim
424
+ context = torch.concat([context_clip, context], dim=1)
425
+
426
+ # arguments
427
+ kwargs = dict(
428
+ e=e0,
429
+ seq_lens=seq_lens,
430
+ grid_sizes=grid_sizes,
431
+ freqs=self.freqs,
432
+ context=context,
433
+ context_lens=context_lens)
434
+
435
+ if self.use_context_parallel:
436
+ x = torch.chunk(x, get_world_size(), dim=1)[get_rank()]
437
+
438
+ for idx, block in enumerate(self.blocks):
439
+ x = block(x, **kwargs)
440
+ x = self.after_transformer_block(idx, x, motion_vec)
441
+
442
+ # head
443
+ x = self.head(x, e)
444
+
445
+ if self.use_context_parallel:
446
+ x = gather_forward(x, dim=1)
447
+
448
+ # unpatchify
449
+ x = self.unpatchify(x, grid_sizes)
450
+ return [u.float() for u in x]
451
+
452
+
453
+ def unpatchify(self, x, grid_sizes):
454
+ r"""
455
+ Reconstruct video tensors from patch embeddings.
456
+
457
+ Args:
458
+ x (List[Tensor]):
459
+ List of patchified features, each with shape [L, C_out * prod(patch_size)]
460
+ grid_sizes (Tensor):
461
+ Original spatial-temporal grid dimensions before patching,
462
+ shape [B, 3] (3 dimensions correspond to F_patches, H_patches, W_patches)
463
+
464
+ Returns:
465
+ List[Tensor]:
466
+ Reconstructed video tensors with shape [C_out, F, H / 8, W / 8]
467
+ """
468
+
469
+ c = self.out_dim
470
+ out = []
471
+ for u, v in zip(x, grid_sizes.tolist()):
472
+ u = u[:math.prod(v)].view(*v, *self.patch_size, c)
473
+ u = torch.einsum('fhwpqrc->cfphqwr', u)
474
+ u = u.reshape(c, *[i * j for i, j in zip(v, self.patch_size)])
475
+ out.append(u)
476
+ return out
477
+
478
+ def init_weights(self):
479
+ r"""
480
+ Initialize model parameters using Xavier initialization.
481
+ """
482
+
483
+ # basic init
484
+ for m in self.modules():
485
+ if isinstance(m, nn.Linear):
486
+ nn.init.xavier_uniform_(m.weight)
487
+ if m.bias is not None:
488
+ nn.init.zeros_(m.bias)
489
+
490
+ # init embeddings
491
+ nn.init.xavier_uniform_(self.patch_embedding.weight.flatten(1))
492
+ for m in self.text_embedding.modules():
493
+ if isinstance(m, nn.Linear):
494
+ nn.init.normal_(m.weight, std=.02)
495
+ for m in self.time_embedding.modules():
496
+ if isinstance(m, nn.Linear):
497
+ nn.init.normal_(m.weight, std=.02)
498
+
499
+ # init output layer
500
+ nn.init.zeros_(self.head.head.weight)
wan/modules/animate/motion_encoder.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from ``https://github.com/wyhsirius/LIA``
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+ from torch.nn import functional as F
6
+ import math
7
+
8
+ def custom_qr(input_tensor):
9
+ original_dtype = input_tensor.dtype
10
+ if original_dtype == torch.bfloat16:
11
+ q, r = torch.linalg.qr(input_tensor.to(torch.float32))
12
+ return q.to(original_dtype), r.to(original_dtype)
13
+ return torch.linalg.qr(input_tensor)
14
+
15
+ def fused_leaky_relu(input, bias, negative_slope=0.2, scale=2 ** 0.5):
16
+ return F.leaky_relu(input + bias, negative_slope) * scale
17
+
18
+
19
+ def upfirdn2d_native(input, kernel, up_x, up_y, down_x, down_y, pad_x0, pad_x1, pad_y0, pad_y1):
20
+ _, minor, in_h, in_w = input.shape
21
+ kernel_h, kernel_w = kernel.shape
22
+
23
+ out = input.view(-1, minor, in_h, 1, in_w, 1)
24
+ out = F.pad(out, [0, up_x - 1, 0, 0, 0, up_y - 1, 0, 0])
25
+ out = out.view(-1, minor, in_h * up_y, in_w * up_x)
26
+
27
+ out = F.pad(out, [max(pad_x0, 0), max(pad_x1, 0), max(pad_y0, 0), max(pad_y1, 0)])
28
+ out = out[:, :, max(-pad_y0, 0): out.shape[2] - max(-pad_y1, 0),
29
+ max(-pad_x0, 0): out.shape[3] - max(-pad_x1, 0), ]
30
+
31
+ out = out.reshape([-1, 1, in_h * up_y + pad_y0 + pad_y1, in_w * up_x + pad_x0 + pad_x1])
32
+ w = torch.flip(kernel, [0, 1]).view(1, 1, kernel_h, kernel_w)
33
+ out = F.conv2d(out, w)
34
+ out = out.reshape(-1, minor, in_h * up_y + pad_y0 + pad_y1 - kernel_h + 1,
35
+ in_w * up_x + pad_x0 + pad_x1 - kernel_w + 1, )
36
+ return out[:, :, ::down_y, ::down_x]
37
+
38
+
39
+ def upfirdn2d(input, kernel, up=1, down=1, pad=(0, 0)):
40
+ return upfirdn2d_native(input, kernel, up, up, down, down, pad[0], pad[1], pad[0], pad[1])
41
+
42
+
43
+ def make_kernel(k):
44
+ k = torch.tensor(k, dtype=torch.float32)
45
+ if k.ndim == 1:
46
+ k = k[None, :] * k[:, None]
47
+ k /= k.sum()
48
+ return k
49
+
50
+
51
+ class FusedLeakyReLU(nn.Module):
52
+ def __init__(self, channel, negative_slope=0.2, scale=2 ** 0.5):
53
+ super().__init__()
54
+ self.bias = nn.Parameter(torch.zeros(1, channel, 1, 1))
55
+ self.negative_slope = negative_slope
56
+ self.scale = scale
57
+
58
+ def forward(self, input):
59
+ out = fused_leaky_relu(input, self.bias, self.negative_slope, self.scale)
60
+ return out
61
+
62
+
63
+ class Blur(nn.Module):
64
+ def __init__(self, kernel, pad, upsample_factor=1):
65
+ super().__init__()
66
+
67
+ kernel = make_kernel(kernel)
68
+
69
+ if upsample_factor > 1:
70
+ kernel = kernel * (upsample_factor ** 2)
71
+
72
+ self.register_buffer('kernel', kernel)
73
+
74
+ self.pad = pad
75
+
76
+ def forward(self, input):
77
+ return upfirdn2d(input, self.kernel, pad=self.pad)
78
+
79
+
80
+ class ScaledLeakyReLU(nn.Module):
81
+ def __init__(self, negative_slope=0.2):
82
+ super().__init__()
83
+
84
+ self.negative_slope = negative_slope
85
+
86
+ def forward(self, input):
87
+ return F.leaky_relu(input, negative_slope=self.negative_slope)
88
+
89
+
90
+ class EqualConv2d(nn.Module):
91
+ def __init__(self, in_channel, out_channel, kernel_size, stride=1, padding=0, bias=True):
92
+ super().__init__()
93
+
94
+ self.weight = nn.Parameter(torch.randn(out_channel, in_channel, kernel_size, kernel_size))
95
+ self.scale = 1 / math.sqrt(in_channel * kernel_size ** 2)
96
+
97
+ self.stride = stride
98
+ self.padding = padding
99
+
100
+ if bias:
101
+ self.bias = nn.Parameter(torch.zeros(out_channel))
102
+ else:
103
+ self.bias = None
104
+
105
+ def forward(self, input):
106
+
107
+ return F.conv2d(input, self.weight * self.scale, bias=self.bias, stride=self.stride, padding=self.padding)
108
+
109
+ def __repr__(self):
110
+ return (
111
+ f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]},'
112
+ f' {self.weight.shape[2]}, stride={self.stride}, padding={self.padding})'
113
+ )
114
+
115
+
116
+ class EqualLinear(nn.Module):
117
+ def __init__(self, in_dim, out_dim, bias=True, bias_init=0, lr_mul=1, activation=None):
118
+ super().__init__()
119
+
120
+ self.weight = nn.Parameter(torch.randn(out_dim, in_dim).div_(lr_mul))
121
+
122
+ if bias:
123
+ self.bias = nn.Parameter(torch.zeros(out_dim).fill_(bias_init))
124
+ else:
125
+ self.bias = None
126
+
127
+ self.activation = activation
128
+
129
+ self.scale = (1 / math.sqrt(in_dim)) * lr_mul
130
+ self.lr_mul = lr_mul
131
+
132
+ def forward(self, input):
133
+
134
+ if self.activation:
135
+ out = F.linear(input, self.weight * self.scale)
136
+ out = fused_leaky_relu(out, self.bias * self.lr_mul)
137
+ else:
138
+ out = F.linear(input, self.weight * self.scale, bias=self.bias * self.lr_mul)
139
+
140
+ return out
141
+
142
+ def __repr__(self):
143
+ return (f'{self.__class__.__name__}({self.weight.shape[1]}, {self.weight.shape[0]})')
144
+
145
+
146
+ class ConvLayer(nn.Sequential):
147
+ def __init__(
148
+ self,
149
+ in_channel,
150
+ out_channel,
151
+ kernel_size,
152
+ downsample=False,
153
+ blur_kernel=[1, 3, 3, 1],
154
+ bias=True,
155
+ activate=True,
156
+ ):
157
+ layers = []
158
+
159
+ if downsample:
160
+ factor = 2
161
+ p = (len(blur_kernel) - factor) + (kernel_size - 1)
162
+ pad0 = (p + 1) // 2
163
+ pad1 = p // 2
164
+
165
+ layers.append(Blur(blur_kernel, pad=(pad0, pad1)))
166
+
167
+ stride = 2
168
+ self.padding = 0
169
+
170
+ else:
171
+ stride = 1
172
+ self.padding = kernel_size // 2
173
+
174
+ layers.append(EqualConv2d(in_channel, out_channel, kernel_size, padding=self.padding, stride=stride,
175
+ bias=bias and not activate))
176
+
177
+ if activate:
178
+ if bias:
179
+ layers.append(FusedLeakyReLU(out_channel))
180
+ else:
181
+ layers.append(ScaledLeakyReLU(0.2))
182
+
183
+ super().__init__(*layers)
184
+
185
+
186
+ class ResBlock(nn.Module):
187
+ def __init__(self, in_channel, out_channel, blur_kernel=[1, 3, 3, 1]):
188
+ super().__init__()
189
+
190
+ self.conv1 = ConvLayer(in_channel, in_channel, 3)
191
+ self.conv2 = ConvLayer(in_channel, out_channel, 3, downsample=True)
192
+
193
+ self.skip = ConvLayer(in_channel, out_channel, 1, downsample=True, activate=False, bias=False)
194
+
195
+ def forward(self, input):
196
+ out = self.conv1(input)
197
+ out = self.conv2(out)
198
+
199
+ skip = self.skip(input)
200
+ out = (out + skip) / math.sqrt(2)
201
+
202
+ return out
203
+
204
+
205
+ class EncoderApp(nn.Module):
206
+ def __init__(self, size, w_dim=512):
207
+ super(EncoderApp, self).__init__()
208
+
209
+ channels = {
210
+ 4: 512,
211
+ 8: 512,
212
+ 16: 512,
213
+ 32: 512,
214
+ 64: 256,
215
+ 128: 128,
216
+ 256: 64,
217
+ 512: 32,
218
+ 1024: 16
219
+ }
220
+
221
+ self.w_dim = w_dim
222
+ log_size = int(math.log(size, 2))
223
+
224
+ self.convs = nn.ModuleList()
225
+ self.convs.append(ConvLayer(3, channels[size], 1))
226
+
227
+ in_channel = channels[size]
228
+ for i in range(log_size, 2, -1):
229
+ out_channel = channels[2 ** (i - 1)]
230
+ self.convs.append(ResBlock(in_channel, out_channel))
231
+ in_channel = out_channel
232
+
233
+ self.convs.append(EqualConv2d(in_channel, self.w_dim, 4, padding=0, bias=False))
234
+
235
+ def forward(self, x):
236
+
237
+ res = []
238
+ h = x
239
+ for conv in self.convs:
240
+ h = conv(h)
241
+ res.append(h)
242
+
243
+ return res[-1].squeeze(-1).squeeze(-1), res[::-1][2:]
244
+
245
+
246
+ class Encoder(nn.Module):
247
+ def __init__(self, size, dim=512, dim_motion=20):
248
+ super(Encoder, self).__init__()
249
+
250
+ # appearance netmork
251
+ self.net_app = EncoderApp(size, dim)
252
+
253
+ # motion network
254
+ fc = [EqualLinear(dim, dim)]
255
+ for i in range(3):
256
+ fc.append(EqualLinear(dim, dim))
257
+
258
+ fc.append(EqualLinear(dim, dim_motion))
259
+ self.fc = nn.Sequential(*fc)
260
+
261
+ def enc_app(self, x):
262
+ h_source = self.net_app(x)
263
+ return h_source
264
+
265
+ def enc_motion(self, x):
266
+ h, _ = self.net_app(x)
267
+ h_motion = self.fc(h)
268
+ return h_motion
269
+
270
+
271
+ class Direction(nn.Module):
272
+ def __init__(self, motion_dim):
273
+ super(Direction, self).__init__()
274
+ self.weight = nn.Parameter(torch.randn(512, motion_dim))
275
+
276
+ def forward(self, input):
277
+
278
+ weight = self.weight + 1e-8
279
+ Q, R = custom_qr(weight)
280
+ if input is None:
281
+ return Q
282
+ else:
283
+ input_diag = torch.diag_embed(input) # alpha, diagonal matrix
284
+ out = torch.matmul(input_diag, Q.T)
285
+ out = torch.sum(out, dim=1)
286
+ return out
287
+
288
+
289
+ class Synthesis(nn.Module):
290
+ def __init__(self, motion_dim):
291
+ super(Synthesis, self).__init__()
292
+ self.direction = Direction(motion_dim)
293
+
294
+
295
+ class Generator(nn.Module):
296
+ def __init__(self, size, style_dim=512, motion_dim=20):
297
+ super().__init__()
298
+
299
+ self.enc = Encoder(size, style_dim, motion_dim)
300
+ self.dec = Synthesis(motion_dim)
301
+
302
+ def get_motion(self, img):
303
+ #motion_feat = self.enc.enc_motion(img)
304
+ motion_feat = torch.utils.checkpoint.checkpoint((self.enc.enc_motion), img, use_reentrant=True)
305
+ with torch.cuda.amp.autocast(dtype=torch.float32):
306
+ motion = self.dec.direction(motion_feat)
307
+ return motion