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
- .gitattributes +1 -1
- README.md +113 -0
- Wan2.1_VAE.pth +3 -0
- generate_prequant.py +425 -0
- high_noise_model_bnb_nf4/config.json +15 -0
- high_noise_model_bnb_nf4/model.safetensors +3 -0
- high_noise_model_bnb_nf4/quantization_meta.json +54 -0
- load_prequant.py +412 -0
- low_noise_model_bnb_nf4/config.json +15 -0
- low_noise_model_bnb_nf4/model.safetensors +3 -0
- low_noise_model_bnb_nf4/quantization_meta.json +54 -0
- models_t5_umt5-xxl-enc-bf16.pth +3 -0
- requirements.txt +15 -0
- tokenizer/special_tokens_map.json +3 -0
- tokenizer/tokenizer.json +3 -0
- tokenizer/tokenizer_config.json +3 -0
- wan/__init__.py +2 -0
- wan/__pycache__/__init__.cpython-310.pyc +0 -0
- wan/__pycache__/image2video.cpython-310.pyc +0 -0
- wan/configs/__init__.py +36 -0
- wan/configs/__pycache__/__init__.cpython-310.pyc +0 -0
- wan/configs/__pycache__/shared_config.cpython-310.pyc +0 -0
- wan/configs/__pycache__/wan_i2v_A14B.cpython-310.pyc +0 -0
- wan/configs/shared_config.py +19 -0
- wan/configs/wan_i2v_A14B.py +37 -0
- wan/distributed/__init__.py +0 -0
- wan/distributed/__pycache__/__init__.cpython-310.pyc +0 -0
- wan/distributed/__pycache__/fsdp.cpython-310.pyc +0 -0
- wan/distributed/__pycache__/sequence_parallel.cpython-310.pyc +0 -0
- wan/distributed/__pycache__/ulysses.cpython-310.pyc +0 -0
- wan/distributed/__pycache__/util.cpython-310.pyc +0 -0
- wan/distributed/fsdp.py +44 -0
- wan/distributed/sequence_parallel.py +213 -0
- wan/distributed/ulysses.py +46 -0
- wan/distributed/util.py +50 -0
- wan/image2video.py +489 -0
- wan/modules/__init__.py +18 -0
- wan/modules/__pycache__/__init__.cpython-310.pyc +0 -0
- wan/modules/__pycache__/attention.cpython-310.pyc +0 -0
- wan/modules/__pycache__/model.cpython-310.pyc +0 -0
- wan/modules/__pycache__/t5.cpython-310.pyc +0 -0
- wan/modules/__pycache__/tokenizers.cpython-310.pyc +0 -0
- wan/modules/__pycache__/vae2_1.cpython-310.pyc +0 -0
- wan/modules/__pycache__/vae2_2.cpython-310.pyc +0 -0
- wan/modules/animate/__init__.py +4 -0
- wan/modules/animate/animate_utils.py +143 -0
- wan/modules/animate/clip.py +542 -0
- wan/modules/animate/face_blocks.py +383 -0
- wan/modules/animate/model_animate.py +500 -0
- 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
|