add the training script for taew2_2_super
This commit is contained in:
@@ -0,0 +1,434 @@
|
||||
# TAE Super (Tiny AutoEncoder) Training Guide
|
||||
|
||||
This document provides a complete workflow for training / fine-tuning the Wan2.2
|
||||
Super decoder (`AutoencoderTinyWan`), including environment setup, data
|
||||
preparation, training, and using the trained checkpoint for inference.
|
||||
|
||||
> **Note**: Training uses the frozen official Wan2.2 encoder and updates only the
|
||||
> Super decoder's approximately 19M parameters. The tiny encoder is frozen and is
|
||||
> not used in the training forward pass.
|
||||
>
|
||||
> ```text
|
||||
> x (video, [-1, 1])
|
||||
> -> official Wan2.2 encoder (frozen) : z = vae.encode(x).latent_dist.mode()
|
||||
> -> Super decoder : x_hat = tae(z).sample
|
||||
> loss = 0.1 L1 + 0.1 LPIPS + 0.02 DINO + 1.0 grid
|
||||
> ```
|
||||
>
|
||||
> The losses compare reconstruction and input after mapping RGB to [0,1]. The
|
||||
> decoder learns to consume the official Wan latent space directly.
|
||||
|
||||
The script supports the Wan2.2 5B Super model:
|
||||
|
||||
| Family | Latent | Official full VAE | `--config_path` | Models |
|
||||
|--------|--------|-------------------|-----------------|--------|
|
||||
| taew2_2_super | 48ch, patch_size=2 | `AutoencoderKLWan3_8` | `config/wan2.2/wan_civitai_5b.yaml` | Wan2.2 TI2V-5B / Fun-2.2VAE |
|
||||
|
||||
**Differences from [train_taehv.py](train_taehv.py)**:
|
||||
|
||||
| Item | `train_taehv.py` | `train_taehv_super.py` |
|
||||
|------|-----------------|-----------------------|
|
||||
| Reconstruction path | Tiny encoder → tiny decoder; official encoder supplies latent targets | Frozen official encoder → Super decoder |
|
||||
| Trainable parameters | Tiny encoder and decoder by default, controlled by `--trainable_modules` | Super decoder only; official and tiny encoders are frozen |
|
||||
| Losses | Pixel L1 + latent MSE | Pixel L1 + LPIPS + DINO + grid |
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [1. Environment Setup](#1-environment-setup)
|
||||
- [2. Data Preparation](#2-data-preparation)
|
||||
- [2.1 Quick Test Dataset](#21-quick-test-dataset)
|
||||
- [2.2 Dataset Structure](#22-dataset-structure)
|
||||
- [2.3 metadata.json Format](#23-metadatajson-format)
|
||||
- [2.4 Relative vs Absolute Path Usage](#24-relative-vs-absolute-path-usage)
|
||||
- [3. TAE Super Training](#3-tae-super-training)
|
||||
- [3.1 Download Pretrained Model](#31-download-pretrained-model)
|
||||
- [3.2 Quick Start](#32-quick-start)
|
||||
- [3.3 Training Parameter Reference](#33-training-parameter-reference)
|
||||
- [3.4 Training Validation](#34-training-validation)
|
||||
- [3.5 Training Tips](#35-training-tips)
|
||||
- [3.6 Further Tuning](#36-further-tuning)
|
||||
- [4. Inference Testing](#4-inference-testing)
|
||||
- [4.1 Checkpoint Layout](#41-checkpoint-layout)
|
||||
- [4.2 Use the Trained TAE in Predict Scripts](#42-use-the-trained-tae-in-predict-scripts)
|
||||
- [5. Additional Resources](#5-additional-resources)
|
||||
|
||||
---
|
||||
|
||||
## 1. Environment Setup
|
||||
|
||||
Run the commands from the VideoX-Fun repository root. Install dependencies into
|
||||
your Python environment with pip.
|
||||
|
||||
**Option 1: Using requirements.txt**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install lpips torchvision av modelscope opencv-python
|
||||
```
|
||||
|
||||
**Option 2: Manual Installation**
|
||||
|
||||
```bash
|
||||
pip install Pillow einops safetensors timm tomesd librosa "torch>=2.1.2" torchdiffeq torchsde decord datasets numpy scikit-image
|
||||
pip install omegaconf SentencePiece imageio[ffmpeg] imageio[pyav] tensorboard beautifulsoup4 ftfy func_timeout onnxruntime
|
||||
pip install "peft>=0.17.0" "accelerate>=0.25.0" "gradio>=3.41.2" "diffusers>=0.30.1" "transformers>=4.46.2"
|
||||
pip install lpips torchvision av modelscope opencv-python
|
||||
```
|
||||
|
||||
LPIPS's AlexNet backbone and DINOv2 download automatically when missing and reuse
|
||||
the PyTorch cache. LPIPS's calibration weights ship with the package. For offline
|
||||
DINOv2 loading, use `--dino_repo` and `--dino_weights`.
|
||||
|
||||
> The script supports a single device and ordinary DDP through Accelerate. The
|
||||
> official Wan VAE and the frozen LPIPS/DINOv2 feature networks remain on the GPU.
|
||||
|
||||
---
|
||||
|
||||
## 2. Data Preparation
|
||||
|
||||
### 2.1 Quick Test Dataset
|
||||
|
||||
We provide a test dataset containing several training samples.
|
||||
|
||||
```bash
|
||||
modelscope download --dataset PAI/X-Fun-Videos-Demo --local_dir ./datasets/X-Fun-Videos-Demo
|
||||
```
|
||||
|
||||
### 2.2 Dataset Structure
|
||||
|
||||
```
|
||||
📦 datasets/
|
||||
├── 📂 my_dataset/
|
||||
│ ├── 📂 train/
|
||||
│ │ ├── 📄 video001.mp4
|
||||
│ │ ├── 📄 video002.mp4
|
||||
│ │ └── 📄 ...
|
||||
│ └── 📄 metadata.json
|
||||
```
|
||||
|
||||
### 2.3 metadata.json Format
|
||||
|
||||
**Relative Path Format** (example format):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "train/video001.mp4",
|
||||
"text": "A beautiful sunset over the ocean, golden hour lighting",
|
||||
"type": "video",
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Absolute Path Format**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "/mnt/data/videos/sunset.mp4",
|
||||
"text": "A beautiful sunset over the ocean",
|
||||
"type": "video",
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Key Field Descriptions**:
|
||||
|
||||
- `file_path`: Video path (relative or absolute path)
|
||||
- `text`: Video description (not used by the TAE loss, kept for meta format compatibility)
|
||||
- `type`: Data type, should be `"video"`
|
||||
- `width` / `height`: Video width and height (recommended metadata). The Super
|
||||
training entry uses a fixed resize/crop canvas.
|
||||
|
||||
### 2.4 Relative vs Absolute Path Usage
|
||||
|
||||
**Relative Path**:
|
||||
|
||||
```bash
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
```
|
||||
|
||||
**Absolute Path**:
|
||||
|
||||
```bash
|
||||
export DATASET_NAME=""
|
||||
export DATASET_META_NAME="/mnt/data/metadata.json"
|
||||
```
|
||||
|
||||
> 💡 **Recommendation**: If the dataset is small and stored locally, use relative paths. If the dataset is stored on external storage (e.g., NAS, OSS) or shared across multiple machines, use absolute paths.
|
||||
|
||||
---
|
||||
|
||||
## 3. TAE Super Training
|
||||
|
||||
### 3.1 Download Pretrained Model
|
||||
|
||||
The training script needs the official Wan2.2 VAE weights, which ship inside the
|
||||
model directory. Download the released Super weights for fine-tuning:
|
||||
|
||||
```bash
|
||||
mkdir -p models/Diffusion_Transformer
|
||||
|
||||
modelscope download --model Wan-AI/Wan2.2-TI2V-5B --local_dir models/Diffusion_Transformer/Wan2.2-TI2V-5B
|
||||
|
||||
wget -O models/Diffusion_Transformer/taew2_2_super.pth \
|
||||
https://github.com/madebyollin/taehv/raw/main/taew2_2_super.pth
|
||||
```
|
||||
|
||||
LPIPS and DINOv2 weights are loaded separately by the loss implementation.
|
||||
|
||||
### 3.2 Quick Start
|
||||
|
||||
**Wan2.2 TI2V-5B / Fun-2.2VAE (taew2_2_super) Example**:
|
||||
|
||||
The launcher uses a 720x1280 canvas, 25 training frames, 81 validation frames,
|
||||
10,000 updates, and EMA. Set the GPU/process count with `accelerate config`;
|
||||
four GPUs with batch size 1 and accumulation 1 give an effective batch of 4.
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.2-TI2V-5B"
|
||||
export DATASET_NAME="${DATASET_NAME:-datasets/X-Fun-Videos-Demo}"
|
||||
export DATASET_META_NAME="${DATASET_META_NAME:-$DATASET_NAME/metadata_add_width_height.json}"
|
||||
export TAE_PATH="models/Diffusion_Transformer/taew2_2_super.pth"
|
||||
NCCL_DEBUG=INFO
|
||||
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--config_path="config/wan2.2/wan_civitai_5b.yaml" \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--tae_path="$TAE_PATH" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--fix_sample_size 720 1280 \
|
||||
--video_sample_strides 1 2 3 \
|
||||
--video_sample_n_frames=25 \
|
||||
--validation_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--max_train_steps=10000 \
|
||||
--checkpointing_steps=1000 \
|
||||
--validation_steps=1000 \
|
||||
--validation_max_samples=0 \
|
||||
--learning_rate=5e-06 \
|
||||
--lr_scheduler="cosine" \
|
||||
--lr_warmup_steps=200 \
|
||||
--lr_min_ratio=0.2 \
|
||||
--l1_loss_weight=0.1 \
|
||||
--lpips_loss_weight=0.1 \
|
||||
--dino_loss_weight=0.02 \
|
||||
--grid_loss_weight=1.0 \
|
||||
--grid_period=16 \
|
||||
--feature_batch_size=13 \
|
||||
--use_ema \
|
||||
--ema_decay=0.999 \
|
||||
--selection_lpips_ratio=1.02 \
|
||||
--selection_grid_ratio=1.05 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_taehv_w2.2_super" \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_beta1=0.9 \
|
||||
--adam_beta2=0.99 \
|
||||
--adam_weight_decay=1e-2 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=1.0
|
||||
```
|
||||
|
||||
These settings are also provided in `scripts/taehv/train_taehv_super.sh`. Edit its
|
||||
training arguments and run `bash scripts/taehv/train_taehv_super.sh`.
|
||||
|
||||
**Train from scratch**:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--from_scratch \
|
||||
--fix_sample_size 720 1280 \
|
||||
--video_sample_n_frames=25 \
|
||||
--video_sample_strides 1 2 3 \
|
||||
--max_train_steps=10000 \
|
||||
--learning_rate=5e-6 --lr_warmup_steps=200 --lr_min_ratio=0.2 \
|
||||
--use_ema --ema_decay=0.999 \
|
||||
--dataloader_num_workers=8 --checkpointing_steps=1000 --validation_steps=1000 \
|
||||
--output_dir="output_dir_taehv_w2.2_super_scratch"
|
||||
```
|
||||
|
||||
Omit `--tae_path` when using `--from_scratch`. Only the decoder is trained; the
|
||||
saved tiny encoder remains randomly initialized in a scratch run.
|
||||
|
||||
**Resume interrupted training**:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--fix_sample_size 720 1280 --video_sample_n_frames=25 \
|
||||
--video_sample_strides 1 2 3 --max_train_steps=10000 \
|
||||
--learning_rate=5e-6 --lr_warmup_steps=200 --lr_min_ratio=0.2 \
|
||||
--use_ema --ema_decay=0.999 \
|
||||
--dataloader_num_workers=8 --checkpointing_steps=1000 --validation_steps=1000 \
|
||||
--output_dir="output_dir_taehv_w2.2_super" \
|
||||
--resume_from_checkpoint=latest
|
||||
```
|
||||
|
||||
Restore with the same settings used to create the checkpoint, including validation
|
||||
metadata when supplied. `--max_train_steps` is the total update budget. Keep the
|
||||
original schedule horizon. The checkpoint restores optimizer, scheduler, EMA, RNG,
|
||||
step and batch cursor; changing workers or sampling settings can change the resumed
|
||||
sample sequence. Use a new output directory for a new run.
|
||||
|
||||
### 3.3 Training Parameter Reference
|
||||
|
||||
Values below describe the launcher configuration; they are not all Python CLI defaults.
|
||||
|
||||
| Parameter | Description | Example Value |
|
||||
|-----------|-------------|---------------|
|
||||
| `--config_path` | Official Wan VAE component configuration | `config/wan2.2/wan_civitai_5b.yaml` |
|
||||
| `--pretrained_model_name_or_path` | Model directory containing the official VAE weights | `models/Diffusion_Transformer/Wan2.2-TI2V-5B` |
|
||||
| `--tae_path` | Super weights, file or Diffusers directory; omit to initialize randomly | `models/Diffusion_Transformer/taew2_2_super.pth` |
|
||||
| `--from_scratch` | Randomly initialize the Super decoder; excludes weight initialization and resume | - |
|
||||
| `--train_data_dir` / `--train_data_meta` | Video root and JSON metadata array | `datasets/X-Fun-Videos-Demo` |
|
||||
| `--fix_sample_size` | Fixed `[height, width]`, overriding square `--video_sample_size` | `720 1280` |
|
||||
| `--video_sample_strides` | Choose one sampling stride per training clip | `1 2 3` |
|
||||
| `--video_sample_n_frames` | Training clip length, must be 4k+1 | 25 |
|
||||
| `--train_batch_size` / `--gradient_accumulation_steps` | Batch per device / gradient accumulation | 1 / 1 |
|
||||
| `--vae_mini_batch` | Official VAE encoding mini-batch size | 1 |
|
||||
| `--dataloader_num_workers` | DataLoader workers | 8 |
|
||||
| `--max_train_steps` | Total optimizer updates; overrides `--num_train_epochs` | 10000 |
|
||||
| `--checkpointing_steps` / `--checkpoints_total_limit` | Save interval / checkpoint count limit | 1000 / `None` |
|
||||
| `--learning_rate` / `--lr_scheduler` | Peak learning rate / scheduler | `5e-6` / `cosine` |
|
||||
| `--lr_warmup_steps` / `--lr_min_ratio` | Warmup updates / final-to-peak ratio for cosine decay | 200 / 0.2 |
|
||||
| `--l1_loss_weight` / `--lpips_loss_weight` | Pixel L1 / LPIPS loss weights | 0.1 / 0.1 |
|
||||
| `--dino_loss_weight` / `--grid_loss_weight` | DINO / periodic grid loss weights | 0.02 / 1.0 |
|
||||
| `--feature_batch_size` / `--feature_checkpointing` | Perceptual frame batch / activation checkpointing | 13 / off |
|
||||
| `--use_ema` / `--ema_decay` | Maintain decoder EMA for validation and selection | enabled / 0.999 |
|
||||
| `--seed` / `--mixed_precision` | Random seed / mixed precision | 42 / `bf16` |
|
||||
| `--output_dir` | Output directory | `output_dir_taehv_w2.2_super` |
|
||||
| `--resume_from_checkpoint` | Restore a checkpoint directory or select the latest | `latest` |
|
||||
|
||||
**Sample Size Configuration Guide**:
|
||||
|
||||
- `--fix_sample_size 720 1280` resizes to cover the canvas and center crops while preserving aspect ratio. Without it, `--video_sample_size` selects a square canvas.
|
||||
- A 25-frame clip at stride 3 needs at least 73 source frames; 81-frame validation needs at least 81. Short inputs shorten the batch to the shortest 4k+1 clip length, recorded as `frames` in the training log.
|
||||
- Training uses random temporal starts. Validation starts at frame 0 with stride 1. Data loading retains the original random replacement/retry behavior on decode failures.
|
||||
|
||||
**Loss Configuration Guide**:
|
||||
|
||||
- The frozen official encoder supplies latents; only the Super decoder is optimized.
|
||||
- LPIPS uses AlexNet; DINOv2 uses intermediate ViT-B/14 patch features. Frozen feature networks still pass reconstruction gradients to the decoder.
|
||||
- Grid loss measures residual means by spatial phase with each frame/channel's DC removed. Inspect local crops alongside this metric.
|
||||
|
||||
### 3.4 Training Validation
|
||||
|
||||
Provide a separate JSON array through `--validation_data_meta` in the training
|
||||
command, or add that argument to the launcher. Relative video paths resolve under
|
||||
`--train_data_dir`, so use absolute video paths when validation data has another root.
|
||||
|
||||
| Parameter | Description | Recommended Value |
|
||||
|-----------|-------------|-------------------|
|
||||
| `--validation_data_meta` | Held-out video metadata array | Your validation index |
|
||||
| `--validation_steps` | Evaluate every N optimizer updates | 1000 |
|
||||
| `--validation_n_frames` | Validation clip length, 4k+1 | 81 |
|
||||
| `--validation_max_samples` | Validation clip cap; zero uses the complete index | 0 |
|
||||
|
||||
Validation runs at initialization, periodically, and at the final step. It reports
|
||||
PSNR, SSIM, LPIPS and grid, and saves the first sample's input/reconstruction preview
|
||||
as `sample/step-N.mp4`. Metrics use floating-point output before video encoding,
|
||||
averaging frames within a clip and then clips equally.
|
||||
|
||||
With EMA enabled, validation and best-weight selection use EMA; otherwise they
|
||||
use raw weights. Selection maximizes PSNR subject to LPIPS/grid ratios relative
|
||||
to the first validation, controlled by `--selection_lpips_ratio` and
|
||||
`--selection_grid_ratio`. The initialization is also a candidate. Best inference
|
||||
weights go to `best/taehv`; resumable checkpoints retain raw training weights.
|
||||
|
||||
For a short execution check, use the official demo:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" --tae_path="$TAE_PATH" \
|
||||
--train_data_dir="datasets/X-Fun-Videos-Demo" \
|
||||
--train_data_meta="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json" \
|
||||
--validation_data_meta="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json" \
|
||||
--fix_sample_size 64 96 --video_sample_n_frames=5 --validation_n_frames=5 \
|
||||
--video_sample_strides 1 --max_train_steps=2 --lr_warmup_steps=0 \
|
||||
--checkpointing_steps=1 --validation_steps=1 --validation_max_samples=1 \
|
||||
--dataloader_num_workers=0 --use_ema --ema_decay=0.999 \
|
||||
--output_dir="output_dir_taehv_w2.2_super_smoke"
|
||||
```
|
||||
|
||||
Sharing the demo across training/validation checks execution only. Use independent
|
||||
video sources for actual experiments, and use native-resolution data to assess
|
||||
720p quality.
|
||||
|
||||
### 3.5 Training Tips
|
||||
|
||||
- **Warm-start vs. from scratch**: Fine-tune from `taew2_2_super.pth` or a previous Super directory; use `--from_scratch` for a new decoder. Initialization, dataset and budget all affect the result.
|
||||
- **Loss balance**: Start with the four-loss settings in the launcher and inspect both reconstruction metrics and native-resolution crops.
|
||||
- **Memory**: Lower the training frame count, canvas size or feature batch size, or enable `--feature_checkpointing` if needed. Perceptual networks are frozen but their prediction branches still need activation memory.
|
||||
- **EMA**: The launcher enables EMA. `best/taehv` holds selected inference weights; `checkpoint-N/taehv` holds raw weights for training recovery.
|
||||
- **Distributed training**: Use Accelerate's single-device or ordinary DDP configuration. Global batch equals device count times per-device batch times accumulation.
|
||||
|
||||
### 3.6 Further Tuning
|
||||
|
||||
- Try longer temporal context and check both the initial frames and later frames, accounting for the extra compute.
|
||||
- Explore pixel MSE or official-decoder output supervision, balancing pixel accuracy, perceptual quality and teacher compute cost.
|
||||
- Adjust LPIPS/grid weights with controlled comparisons, checking fine textures, text and local grid artifacts alongside average metrics.
|
||||
|
||||
These are possible extensions; the current training entry uses the four-loss recipe above.
|
||||
|
||||
---
|
||||
|
||||
## 4. Inference Testing
|
||||
|
||||
### 4.1 Checkpoint Layout
|
||||
|
||||
Each resumable checkpoint is written as `output_dir/checkpoint-{step}/`:
|
||||
|
||||
```text
|
||||
output_dir_taehv_w2.2_super/
|
||||
args-step-N.yaml # resolved invocation record
|
||||
train.jsonl # total loss, four terms, LR and actual frame count
|
||||
validation.jsonl # metrics and selection decisions, when validation is enabled
|
||||
best/taehv/ # selected inference weights, when validation is enabled
|
||||
checkpoint-1000/
|
||||
taehv/ # raw Super weights and config.json
|
||||
optimizer.bin
|
||||
custom_checkpoint_*.pkl # scheduler and optional EMA state
|
||||
random_states_*.pkl
|
||||
trainer_state.json
|
||||
sample/ # validation videos
|
||||
logs/ # TensorBoard logs
|
||||
```
|
||||
|
||||
The `taehv` directories use Diffusers `save_pretrained` format and load through
|
||||
`AutoencoderTinyWan.from_pretrained`. `args-step-N.yaml` records the invocation;
|
||||
training arguments are configured directly in the shell launcher.
|
||||
|
||||
### 4.2 Use the Trained TAE in Predict Scripts
|
||||
|
||||
Point `tae_path` in a compatible Wan2.2 5B TAE predict script to the selected
|
||||
`best/taehv` directory:
|
||||
|
||||
| Script | Family |
|
||||
|--------|--------|
|
||||
| `examples/wan2.2/predict_ti2v_tae.py` | taew2_2 |
|
||||
| `examples/wan2.2_fun/predict_t2v_2.2vae_tae.py` | taew2_2 |
|
||||
| `examples/wan2.2_fun/predict_i2v_2.2vae_tae.py` | taew2_2 |
|
||||
|
||||
```python
|
||||
# e.g. in examples/wan2.2_fun/predict_t2v_2.2vae_tae.py
|
||||
tae_path = "output_dir_taehv_w2.2_super/best/taehv"
|
||||
```
|
||||
|
||||
The trained decoder consumes official Wan2.2 5B latents. A scratch-trained
|
||||
checkpoint has no trained tiny encoder; retain the official Wan encoder for
|
||||
image/video conditioning when using such a checkpoint.
|
||||
@@ -0,0 +1,419 @@
|
||||
# TAE Super(Tiny AutoEncoder)训练指南
|
||||
|
||||
本文档提供 Wan2.2 Super decoder(`AutoencoderTinyWan`)训练/微调的完整流程,
|
||||
包括环境配置、数据准备、训练,以及将训练好的 checkpoint 用于推理。
|
||||
|
||||
> **说明**:训练使用冻结的官方 Wan2.2 encoder,只更新 Super decoder 约 19M 参数。
|
||||
> Tiny encoder 冻结,不参与训练前向。
|
||||
>
|
||||
> ```text
|
||||
> x (video, [-1, 1])
|
||||
> -> official Wan2.2 encoder (frozen) : z = vae.encode(x).latent_dist.mode()
|
||||
> -> Super decoder : x_hat = tae(z).sample
|
||||
> loss = 0.1 L1 + 0.1 LPIPS + 0.02 DINO + 1.0 grid
|
||||
> ```
|
||||
>
|
||||
> 损失在重建与输入 RGB 映射到 [0,1] 后计算,decoder 直接学习解码官方 Wan latent。
|
||||
|
||||
脚本支持 Wan2.2 5B Super 模型:
|
||||
|
||||
| 家族 | Latent | 官方完整 VAE | `--config_path` | 适用模型 |
|
||||
|------|--------|--------------|-----------------|----------|
|
||||
| taew2_2_super | 48ch, patch_size=2 | `AutoencoderKLWan3_8` | `config/wan2.2/wan_civitai_5b.yaml` | Wan2.2 TI2V-5B / Fun-2.2VAE |
|
||||
|
||||
**与 [train_taehv.py](train_taehv.py) 的差异**:
|
||||
|
||||
| 对比项 | `train_taehv.py` | `train_taehv_super.py` |
|
||||
|--------|-----------------|-----------------------|
|
||||
| 重建路径 | 小 encoder → 小 decoder,官方 encoder 提供 latent 监督 | 冻结官方 encoder → Super decoder |
|
||||
| 更新参数 | 默认训练小 encoder 和 decoder,由 `--trainable_modules` 控制 | 仅 Super decoder;官方和小 encoder 均冻结 |
|
||||
| 损失 | 像素 L1 + latent MSE | 像素 L1 + LPIPS + DINO + grid |
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [一、环境配置](#一环境配置)
|
||||
- [二、数据准备](#二数据准备)
|
||||
- [2.1 快速测试数据集](#21-快速测试数据集)
|
||||
- [2.2 数据集结构](#22-数据集结构)
|
||||
- [2.3 metadata.json 格式](#23-metadatajson-格式)
|
||||
- [2.4 相对路径与绝对路径使用方案](#24-相对路径与绝对路径使用方案)
|
||||
- [三、TAE Super 训练](#三tae-super-训练)
|
||||
- [3.1 下载预训练模型](#31-下载预训练模型)
|
||||
- [3.2 快速开始](#32-快速开始)
|
||||
- [3.3 训练常用参数解析](#33-训练常用参数解析)
|
||||
- [3.4 训练验证](#34-训练验证)
|
||||
- [3.5 训练技巧](#35-训练技巧)
|
||||
- [3.6 后续调优方向](#36-后续调优方向)
|
||||
- [四、推理测试](#四推理测试)
|
||||
- [4.1 Checkpoint 目录结构](#41-checkpoint-目录结构)
|
||||
- [4.2 在 Predict 脚本中使用训练好的 TAE](#42-在-predict-脚本中使用训练好的-tae)
|
||||
- [五、更多资源](#五更多资源)
|
||||
|
||||
---
|
||||
|
||||
## 一、环境配置
|
||||
|
||||
以下命令在 VideoX-Fun 仓库根目录执行,使用 pip 将依赖安装到当前 Python 环境。
|
||||
|
||||
**方式 1:使用 requirements.txt**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
pip install lpips torchvision av modelscope opencv-python
|
||||
```
|
||||
|
||||
**方式 2:手动安装依赖**
|
||||
|
||||
```bash
|
||||
pip install Pillow einops safetensors timm tomesd librosa "torch>=2.1.2" torchdiffeq torchsde decord datasets numpy scikit-image
|
||||
pip install omegaconf SentencePiece imageio[ffmpeg] imageio[pyav] tensorboard beautifulsoup4 ftfy func_timeout onnxruntime
|
||||
pip install "peft>=0.17.0" "accelerate>=0.25.0" "gradio>=3.41.2" "diffusers>=0.30.1" "transformers>=4.46.2"
|
||||
pip install lpips torchvision av modelscope opencv-python
|
||||
```
|
||||
|
||||
LPIPS 的 AlexNet 主干和 DINOv2 在缺少缓存时自动下载,后续复用 PyTorch 缓存;
|
||||
LPIPS 校准权重随安装包提供。离线加载 DINOv2 时,可使用 `--dino_repo` 和 `--dino_weights`。
|
||||
|
||||
> 脚本通过 Accelerate 支持单设备与普通 DDP。官方 Wan VAE 和冻结的 LPIPS/DINOv2
|
||||
> 特征网络均保留在 GPU 上。
|
||||
|
||||
---
|
||||
|
||||
## 二、数据准备
|
||||
|
||||
### 2.1 快速测试数据集
|
||||
|
||||
我们提供了一个测试的数据集,其中包含若干训练数据。
|
||||
|
||||
```bash
|
||||
modelscope download --dataset PAI/X-Fun-Videos-Demo --local_dir ./datasets/X-Fun-Videos-Demo
|
||||
```
|
||||
|
||||
### 2.2 数据集结构
|
||||
|
||||
```
|
||||
📦 datasets/
|
||||
├── 📂 my_dataset/
|
||||
│ ├── 📂 train/
|
||||
│ │ ├── 📄 video001.mp4
|
||||
│ │ ├── 📄 video002.mp4
|
||||
│ │ └── 📄 ...
|
||||
│ └── 📄 metadata.json
|
||||
```
|
||||
|
||||
### 2.3 metadata.json 格式
|
||||
|
||||
**相对路径格式**(示例格式):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "train/video001.mp4",
|
||||
"text": "A beautiful sunset over the ocean, golden hour lighting",
|
||||
"type": "video",
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**绝对路径格式**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "/mnt/data/videos/sunset.mp4",
|
||||
"text": "A beautiful sunset over the ocean",
|
||||
"type": "video",
|
||||
"width": 1024,
|
||||
"height": 1024
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**关键字段说明**:
|
||||
|
||||
- `file_path`:视频路径(相对或绝对路径)
|
||||
- `text`:视频描述(TAE 损失不使用,仅为 meta 格式兼容而保留)
|
||||
- `type`:数据类型,固定为 `"video"`
|
||||
- `width` / `height`:视频宽高(推荐保留)。Super 训练入口使用固定的缩放/裁剪画布。
|
||||
|
||||
### 2.4 相对路径与绝对路径使用方案
|
||||
|
||||
**相对路径**:
|
||||
|
||||
```bash
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
```
|
||||
|
||||
**绝对路径**:
|
||||
|
||||
```bash
|
||||
export DATASET_NAME=""
|
||||
export DATASET_META_NAME="/mnt/data/metadata.json"
|
||||
```
|
||||
|
||||
> 💡 **建议**:如果数据集较小且存储在本地,推荐使用相对路径;如果数据集存储在外部存储(如 NAS、OSS)或多个机器共享存储,推荐使用绝对路径。
|
||||
|
||||
---
|
||||
|
||||
## 三、TAE Super 训练
|
||||
|
||||
### 3.1 下载预训练模型
|
||||
|
||||
训练脚本需要随模型目录提供的官方 Wan2.2 VAE 权重。微调时同时下载发布版 Super 权重:
|
||||
|
||||
```bash
|
||||
mkdir -p models/Diffusion_Transformer
|
||||
|
||||
modelscope download --model Wan-AI/Wan2.2-TI2V-5B --local_dir models/Diffusion_Transformer/Wan2.2-TI2V-5B
|
||||
|
||||
wget -O models/Diffusion_Transformer/taew2_2_super.pth \
|
||||
https://github.com/madebyollin/taehv/raw/main/taew2_2_super.pth
|
||||
```
|
||||
|
||||
LPIPS 与 DINOv2 权重由损失实现单独加载。
|
||||
|
||||
### 3.2 快速开始
|
||||
|
||||
**Wan2.2 TI2V-5B / Fun-2.2VAE(taew2_2_super)训练示例**:
|
||||
|
||||
启动器采用 720x1280 画布、训练 25 帧、验证 81 帧、10,000 步和 EMA。
|
||||
使用 `accelerate config` 配置 GPU/进程数;四卡、每卡 batch=1、累积=1 时有效 batch 为 4。
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.2-TI2V-5B"
|
||||
export DATASET_NAME="${DATASET_NAME:-datasets/X-Fun-Videos-Demo}"
|
||||
export DATASET_META_NAME="${DATASET_META_NAME:-$DATASET_NAME/metadata_add_width_height.json}"
|
||||
export TAE_PATH="models/Diffusion_Transformer/taew2_2_super.pth"
|
||||
NCCL_DEBUG=INFO
|
||||
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--config_path="config/wan2.2/wan_civitai_5b.yaml" \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--tae_path="$TAE_PATH" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--fix_sample_size 720 1280 \
|
||||
--video_sample_strides 1 2 3 \
|
||||
--video_sample_n_frames=25 \
|
||||
--validation_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--max_train_steps=10000 \
|
||||
--checkpointing_steps=1000 \
|
||||
--validation_steps=1000 \
|
||||
--validation_max_samples=0 \
|
||||
--learning_rate=5e-06 \
|
||||
--lr_scheduler="cosine" \
|
||||
--lr_warmup_steps=200 \
|
||||
--lr_min_ratio=0.2 \
|
||||
--l1_loss_weight=0.1 \
|
||||
--lpips_loss_weight=0.1 \
|
||||
--dino_loss_weight=0.02 \
|
||||
--grid_loss_weight=1.0 \
|
||||
--grid_period=16 \
|
||||
--feature_batch_size=13 \
|
||||
--use_ema \
|
||||
--ema_decay=0.999 \
|
||||
--selection_lpips_ratio=1.02 \
|
||||
--selection_grid_ratio=1.05 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_taehv_w2.2_super" \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_beta1=0.9 \
|
||||
--adam_beta2=0.99 \
|
||||
--adam_weight_decay=1e-2 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=1.0
|
||||
```
|
||||
|
||||
相同参数已写入 `scripts/taehv/train_taehv_super.sh`,也可修改其中的训练参数后,
|
||||
运行 `bash scripts/taehv/train_taehv_super.sh`。
|
||||
|
||||
**从头训练**:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--from_scratch \
|
||||
--fix_sample_size 720 1280 \
|
||||
--video_sample_n_frames=25 \
|
||||
--video_sample_strides 1 2 3 \
|
||||
--max_train_steps=10000 \
|
||||
--learning_rate=5e-6 --lr_warmup_steps=200 --lr_min_ratio=0.2 \
|
||||
--use_ema --ema_decay=0.999 \
|
||||
--dataloader_num_workers=8 --checkpointing_steps=1000 --validation_steps=1000 \
|
||||
--output_dir="output_dir_taehv_w2.2_super_scratch"
|
||||
```
|
||||
|
||||
使用 `--from_scratch` 时不要传 `--tae_path`。只训练 decoder,保存的 tiny encoder
|
||||
仍是随机初始化权重。
|
||||
|
||||
**中断后恢复训练**:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--fix_sample_size 720 1280 --video_sample_n_frames=25 \
|
||||
--video_sample_strides 1 2 3 --max_train_steps=10000 \
|
||||
--learning_rate=5e-6 --lr_warmup_steps=200 --lr_min_ratio=0.2 \
|
||||
--use_ema --ema_decay=0.999 \
|
||||
--dataloader_num_workers=8 --checkpointing_steps=1000 --validation_steps=1000 \
|
||||
--output_dir="output_dir_taehv_w2.2_super" \
|
||||
--resume_from_checkpoint=latest
|
||||
```
|
||||
|
||||
恢复时保持创建 checkpoint 时的配置,使用过的验证索引也需继续提供。
|
||||
`--max_train_steps` 表示总更新预算,应保留原 schedule 预算。Checkpoint 恢复
|
||||
optimizer、scheduler、EMA、RNG、step 和 batch 游标;改变 workers 或采样设置可能改变
|
||||
恢复后的样本顺序。新训练应使用新的输出目录。
|
||||
|
||||
### 3.3 训练常用参数解析
|
||||
|
||||
下表列出启动器的配置值,不代表所有参数的 Python CLI 默认值。
|
||||
|
||||
| 参数 | 说明 | 示例值 |
|
||||
|------|------|--------|
|
||||
| `--config_path` | 官方 Wan VAE 组件配置 | `config/wan2.2/wan_civitai_5b.yaml` |
|
||||
| `--pretrained_model_name_or_path` | 包含官方 VAE 权重的模型目录 | `models/Diffusion_Transformer/Wan2.2-TI2V-5B` |
|
||||
| `--tae_path` | Super 权重文件或 Diffusers 目录;不传则随机初始化 | `models/Diffusion_Transformer/taew2_2_super.pth` |
|
||||
| `--from_scratch` | 随机初始化 Super decoder;不能与加载权重或恢复组合 | - |
|
||||
| `--train_data_dir` / `--train_data_meta` | 视频根目录与 JSON 元数据数组 | `datasets/X-Fun-Videos-Demo` |
|
||||
| `--fix_sample_size` | 固定 `[高, 宽]`,覆盖正方形 `--video_sample_size` | `720 1280` |
|
||||
| `--video_sample_strides` | 每个训练片段随机选择一个采样 stride | `1 2 3` |
|
||||
| `--video_sample_n_frames` | 训练片段帧数,必须为 4k+1 | 25 |
|
||||
| `--train_batch_size` / `--gradient_accumulation_steps` | 每卡 batch / 梯度累积 | 1 / 1 |
|
||||
| `--vae_mini_batch` | 官方 VAE 编码 mini-batch | 1 |
|
||||
| `--dataloader_num_workers` | DataLoader workers | 8 |
|
||||
| `--max_train_steps` | 总优化器更新次数,覆盖 `--num_train_epochs` | 10000 |
|
||||
| `--checkpointing_steps` / `--checkpoints_total_limit` | 保存间隔 / checkpoint 数量上限 | 1000 / `None` |
|
||||
| `--learning_rate` / `--lr_scheduler` | 峰值学习率 / 调度器 | `5e-6` / `cosine` |
|
||||
| `--lr_warmup_steps` / `--lr_min_ratio` | 预热步数 / cosine 终点与峰值之比 | 200 / 0.2 |
|
||||
| `--l1_loss_weight` / `--lpips_loss_weight` | 像素 L1 / LPIPS 损失权重 | 0.1 / 0.1 |
|
||||
| `--dino_loss_weight` / `--grid_loss_weight` | DINO / 周期格纹损失权重 | 0.02 / 1.0 |
|
||||
| `--feature_batch_size` / `--feature_checkpointing` | 感知网络帧 batch / 激活 checkpointing | 13 / 关闭 |
|
||||
| `--use_ema` / `--ema_decay` | 维护用于验证和选模的 decoder EMA | 开启 / 0.999 |
|
||||
| `--seed` / `--mixed_precision` | 随机种子 / 混合精度 | 42 / `bf16` |
|
||||
| `--output_dir` | 输出目录 | `output_dir_taehv_w2.2_super` |
|
||||
| `--resume_from_checkpoint` | 恢复 checkpoint 目录或选择最新断点 | `latest` |
|
||||
|
||||
**Sample Size 配置指南**:
|
||||
|
||||
- `--fix_sample_size 720 1280` 等比例缩放覆盖画布后中心裁剪;未指定时,`--video_sample_size` 控制正方形画布。
|
||||
- 25 帧、stride=3 至少需要 73 个源视频帧;81 帧验证至少需要 81 帧。短视频会使 batch 按最短视频截为 4k+1 帧,实际长度记录在训练日志的 `frames` 字段。
|
||||
- 训练随机选择时间起点,验证固定从第 0 帧开始、stride=1。解码失败时沿用原有随机换视频重试逻辑。
|
||||
|
||||
**损失配置指南**:
|
||||
|
||||
- 冻结官方 encoder 提供 latent,只优化 Super decoder。
|
||||
- LPIPS 使用 AlexNet,DINOv2 使用 ViT-B/14 中间 patch 特征;特征网络冻结,但仍将重建分支的梯度传回 decoder。
|
||||
- Grid loss 按空间相位统计残差均值,并去掉每帧每通道的 DC;应结合局部原生裁剪判断伪影。
|
||||
|
||||
### 3.4 训练验证
|
||||
|
||||
在训练命令中通过 `--validation_data_meta` 提供独立 JSON 数组,或将该参数加入启动器。
|
||||
相对视频路径基于 `--train_data_dir` 解析;验证数据位于其他根目录时,可使用绝对视频路径。
|
||||
|
||||
| 参数 | 说明 | 推荐值 |
|
||||
|------|------|--------|
|
||||
| `--validation_data_meta` | 独立验证集元数据数组 | 自己的验证索引 |
|
||||
| `--validation_steps` | 每 N 次优化器更新执行验证 | 1000 |
|
||||
| `--validation_n_frames` | 验证片段帧数,4k+1 | 81 |
|
||||
| `--validation_max_samples` | 验证片段数量上限,0 表示全部 | 0 |
|
||||
|
||||
初始化、周期节点和最后一步均执行验证,报告 PSNR、SSIM、LPIPS 和 grid,并将首个
|
||||
样本的输入/重建预览保存为 `sample/step-N.mp4`。指标使用视频编码前的浮点输出,
|
||||
先对片段内帧平均,再对片段等权平均。
|
||||
|
||||
开启 EMA 时验证和最佳权重选择使用 EMA,否则使用 raw。选模在相对首次验证的
|
||||
LPIPS/grid 比例约束下最大化 PSNR,由 `--selection_lpips_ratio`、`--selection_grid_ratio`
|
||||
控制。初始权重也是候选。最佳推理权重写入 `best/taehv`,可恢复 checkpoint 保留 raw
|
||||
训练权重。
|
||||
|
||||
可使用官方 Demo 进行短流程检查:
|
||||
|
||||
```bash
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" --tae_path="$TAE_PATH" \
|
||||
--train_data_dir="datasets/X-Fun-Videos-Demo" \
|
||||
--train_data_meta="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json" \
|
||||
--validation_data_meta="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json" \
|
||||
--fix_sample_size 64 96 --video_sample_n_frames=5 --validation_n_frames=5 \
|
||||
--video_sample_strides 1 --max_train_steps=2 --lr_warmup_steps=0 \
|
||||
--checkpointing_steps=1 --validation_steps=1 --validation_max_samples=1 \
|
||||
--dataloader_num_workers=0 --use_ema --ema_decay=0.999 \
|
||||
--output_dir="output_dir_taehv_w2.2_super_smoke"
|
||||
```
|
||||
|
||||
短测中训练和验证共用 Demo 只用于检查执行;正式实验应按视频来源隔离,评估 720p
|
||||
质量时应使用原生分辨率数据。
|
||||
|
||||
### 3.5 训练技巧
|
||||
|
||||
- **热启动与从头训练**:从 `taew2_2_super.pth` 或已有 Super 目录微调;使用 `--from_scratch` 训练新 decoder。初始化、数据和预算都会影响结果。
|
||||
- **损失平衡**:先使用启动器中的四项损失配置,同时检查重建指标和原生分辨率裁剪。
|
||||
- **显存**:按需降低训练帧数、画布尺寸或特征 batch,或开启 `--feature_checkpointing`。感知网络虽然冻结,预测分支仍需要激活显存。
|
||||
- **EMA**:启动器默认开启 EMA。`best/taehv` 为选出的推理权重,`checkpoint-N/taehv` 为恢复训练使用的 raw 权重。
|
||||
- **分布式训练**:使用 Accelerate 单设备或普通 DDP 配置。有效 batch 为设备数、每卡 batch 和梯度累积的乘积。
|
||||
|
||||
### 3.6 后续调优方向
|
||||
|
||||
- 尝试更长的时间上下文,同时检查视频起始段和后续帧,并计入额外计算开销。
|
||||
- 探索像素 MSE 或官方 decoder 输出监督,平衡像素精度、感知质量和 teacher 计算成本。
|
||||
- 通过受控对照调整 LPIPS/grid 权重,结合细纹理、文字和局部格纹检查平均指标。
|
||||
|
||||
以上为可探索的扩展方向,当前训练入口使用前述四项损失配置。
|
||||
|
||||
---
|
||||
|
||||
## 四、推理测试
|
||||
|
||||
### 4.1 Checkpoint 目录结构
|
||||
|
||||
可恢复 checkpoint 写入 `output_dir/checkpoint-{step}/`:
|
||||
|
||||
```text
|
||||
output_dir_taehv_w2.2_super/
|
||||
args-step-N.yaml # 实际运行参数记录
|
||||
train.jsonl # 总损失、四项损失、LR 和实际帧数
|
||||
validation.jsonl # 提供验证索引时保存指标与选模结果
|
||||
best/taehv/ # 提供验证索引时保存最佳推理权重
|
||||
checkpoint-1000/
|
||||
taehv/ # raw Super 权重与 config.json
|
||||
optimizer.bin
|
||||
custom_checkpoint_*.pkl # scheduler 和可选 EMA 状态
|
||||
random_states_*.pkl
|
||||
trainer_state.json
|
||||
sample/ # 验证视频
|
||||
logs/ # TensorBoard 日志
|
||||
```
|
||||
|
||||
`taehv` 目录使用 Diffusers `save_pretrained` 格式,可由
|
||||
`AutoencoderTinyWan.from_pretrained` 加载。`args-step-N.yaml` 记录本次调用参数,
|
||||
训练参数直接在 shell 启动器中配置。
|
||||
|
||||
### 4.2 在 Predict 脚本中使用训练好的 TAE
|
||||
|
||||
将兼容的 Wan2.2 5B TAE predict 脚本中的 `tae_path` 指向选出的 `best/taehv` 目录:
|
||||
|
||||
| 脚本 | 家族 |
|
||||
|------|------|
|
||||
| `examples/wan2.2/predict_ti2v_tae.py` | taew2_2 |
|
||||
| `examples/wan2.2_fun/predict_t2v_2.2vae_tae.py` | taew2_2 |
|
||||
| `examples/wan2.2_fun/predict_i2v_2.2vae_tae.py` | taew2_2 |
|
||||
|
||||
```python
|
||||
# 例如 examples/wan2.2_fun/predict_t2v_2.2vae_tae.py 中
|
||||
tae_path = "output_dir_taehv_w2.2_super/best/taehv"
|
||||
```
|
||||
|
||||
训练好的 decoder 接收官方 Wan2.2 5B latent。从头训练的 checkpoint 没有训练好的
|
||||
小 encoder;此类 checkpoint 用于图像/视频条件生成时,应保留官方 Wan encoder。
|
||||
@@ -0,0 +1,949 @@
|
||||
"""Super decoder training, copied and adapted from train_taehv.py.
|
||||
|
||||
Frozen official Wan2.2 encoder -> trainable Super decoder, using the four
|
||||
TinyVAE losses and the stage 4a B training recipe.
|
||||
"""
|
||||
#!/usr/bin/env python
|
||||
# coding=utf-8
|
||||
# Copyright 2025 The VideoX-Fun Team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
|
||||
# ruff: noqa: E402 -- local imports follow the repository sys.path bootstrap.
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import diffusers
|
||||
import torch
|
||||
from accelerate import Accelerator
|
||||
from accelerate.logging import get_logger
|
||||
from accelerate.utils import ProjectConfiguration, set_seed
|
||||
from diffusers.optimization import get_scheduler
|
||||
from diffusers.training_utils import EMAModel
|
||||
from diffusers.utils import check_min_version
|
||||
from omegaconf import OmegaConf
|
||||
from torch import nn
|
||||
from torch.nn import functional as F
|
||||
from torch.utils.checkpoint import checkpoint
|
||||
from torchvision import transforms
|
||||
|
||||
current_file_path = os.path.abspath(__file__)
|
||||
project_roots = [os.path.dirname(current_file_path), os.path.dirname(os.path.dirname(current_file_path)), os.path.dirname(os.path.dirname(os.path.dirname(current_file_path)))]
|
||||
for project_root in project_roots:
|
||||
sys.path.insert(0, project_root) if project_root not in sys.path else None
|
||||
|
||||
from videox_fun.data import ImageVideoSampler, RandomSampler, VideoDataset
|
||||
from videox_fun.models import AutoencoderKLWan3_8, AutoencoderTinyWan
|
||||
from videox_fun.utils.tqdm_bar import PauseAwareTqdm
|
||||
from videox_fun.utils.utils import save_videos_grid
|
||||
|
||||
check_min_version("0.18.0.dev0")
|
||||
logger = get_logger(__name__, log_level="INFO")
|
||||
|
||||
|
||||
# TinyVAE reconstruction losses: RGB [0,1], BCTHW, FP32 reductions.
|
||||
def grid_loss_per_frame(pred, target, period=16):
|
||||
"""Reference: ZYT-World: A Real-Time Controllable World Model for
|
||||
Closed-Loop Autonomous-Driving Simulation (https://arxiv.org/abs/2609.21712).
|
||||
|
||||
Penalize NCHW residual phase means (16x16 by default) after per-frame/channel
|
||||
DC removal to suppress periodic grid artifacts.
|
||||
"""
|
||||
n, c, h, w = pred.shape
|
||||
h, w = h // period * period, w // period * period
|
||||
if min(h, w) == 0:
|
||||
raise ValueError("Images must be at least one grid period in size")
|
||||
residual = pred.float()[..., :h, :w] - target.float()[..., :h, :w]
|
||||
phase = residual.reshape(n, c, h // period, period, w // period, period).mean((2, 4))
|
||||
return (phase - phase.mean((-2, -1), keepdim=True)).abs().mean((1, 2, 3))
|
||||
|
||||
class TinyVAEReconstructionLoss(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
weights,
|
||||
dino_repo=None,
|
||||
dino_weights=None,
|
||||
feature_batch_size=13,
|
||||
feature_checkpointing=False,
|
||||
grid_period=16,
|
||||
lpips_model=None,
|
||||
dino_model=None,
|
||||
):
|
||||
super().__init__()
|
||||
self.weights = {
|
||||
k: float(weights[k]) for k in ("l1", "lpips", "dino", "grid")
|
||||
}
|
||||
if any(not math.isfinite(v) or v < 0 for v in self.weights.values()):
|
||||
raise ValueError("Loss weights must be finite and nonnegative")
|
||||
if not any(self.weights.values()) or min(feature_batch_size, grid_period) < 1:
|
||||
raise ValueError("Need a positive objective, feature batch size and grid period")
|
||||
if lpips_model is None:
|
||||
import lpips
|
||||
|
||||
lpips_model = lpips.LPIPS(net="alex", version="0.1", verbose=False)
|
||||
self.lpips = lpips_model.eval().requires_grad_(False)
|
||||
if self.weights["dino"] and dino_model is None:
|
||||
dino_model = torch.hub.load(
|
||||
dino_repo or "facebookresearch/dinov2:7764ea0f912e53c92e82eb78a2a1631e92725fc8",
|
||||
"dinov2_vitb14",
|
||||
source="local" if dino_repo else "github",
|
||||
pretrained=not bool(dino_weights),
|
||||
trust_repo=True,
|
||||
skip_validation=True,
|
||||
)
|
||||
if dino_weights:
|
||||
dino_model.load_state_dict(
|
||||
torch.load(dino_weights, map_location="cpu", weights_only=True), strict=True
|
||||
)
|
||||
self.dino = dino_model.eval().requires_grad_(False) if self.weights["dino"] else None
|
||||
self.feature_batch_size = feature_batch_size
|
||||
self.feature_checkpointing = feature_checkpointing
|
||||
self.grid_period = grid_period
|
||||
self.register_buffer("mean", torch.tensor([0.485, 0.456, 0.406])[None, :, None, None])
|
||||
self.register_buffer("std", torch.tensor([0.229, 0.224, 0.225])[None, :, None, None])
|
||||
self.eval()
|
||||
|
||||
def train(self, mode=True):
|
||||
return super().train(False)
|
||||
|
||||
def dino_features(self, x):
|
||||
x = F.interpolate(x.float(), (224, 560), mode="bilinear", align_corners=False)
|
||||
return self.dino.get_intermediate_layers(
|
||||
(x - self.mean) / self.std,
|
||||
n=(2, 5, 8, 11),
|
||||
reshape=False,
|
||||
return_class_token=False,
|
||||
norm=True,
|
||||
)
|
||||
|
||||
def feature_terms(self, pred, target):
|
||||
lp = (
|
||||
self.lpips(pred * 2 - 1, target * 2 - 1).float().flatten()
|
||||
if self.weights["lpips"]
|
||||
else pred.new_zeros(len(pred))
|
||||
)
|
||||
semantic = pred.new_zeros(len(pred))
|
||||
if self.dino is not None:
|
||||
with torch.no_grad():
|
||||
reference = self.dino_features(target)
|
||||
features = self.dino_features(pred)
|
||||
semantic = torch.stack(
|
||||
[
|
||||
(fp.float() - ft.float()).square().sum((-2, -1))
|
||||
/ ft.float().square().sum((-2, -1)).clamp_min(1e-8)
|
||||
for fp, ft in zip(features, reference, strict=True)
|
||||
]
|
||||
).mean(0)
|
||||
return lp, semantic
|
||||
|
||||
def forward(self, pred, target):
|
||||
if pred.ndim != 5 or pred.shape != target.shape or pred.shape[1] != 3:
|
||||
raise ValueError("Expected equal BCTHW RGB tensors in [0,1]")
|
||||
p = pred.permute(0, 2, 1, 3, 4).flatten(0, 1).float()
|
||||
x = target.permute(0, 2, 1, 3, 4).flatten(0, 1).float()
|
||||
terms = {
|
||||
"l1": (p - x).abs().mean(),
|
||||
"grid": grid_loss_per_frame(p, x, self.grid_period).mean(),
|
||||
}
|
||||
chunks = []
|
||||
for start in range(0, len(p), self.feature_batch_size):
|
||||
inputs = (
|
||||
p[start : start + self.feature_batch_size],
|
||||
x[start : start + self.feature_batch_size],
|
||||
)
|
||||
if self.feature_checkpointing and torch.is_grad_enabled() and p.requires_grad:
|
||||
chunks.append(checkpoint(self.feature_terms, *inputs, use_reentrant=False))
|
||||
else:
|
||||
chunks.append(self.feature_terms(*inputs))
|
||||
terms.update(
|
||||
lpips=torch.cat([v[0] for v in chunks]).mean(),
|
||||
dino=torch.cat([v[1] for v in chunks]).mean(),
|
||||
)
|
||||
return sum(self.weights[k] * v for k, v in terms.items()), terms
|
||||
|
||||
@torch.no_grad()
|
||||
def metrics(self, pred, target):
|
||||
"""Unweighted frame means; RGB Gaussian 11x11 valid SSIM, no DINO in eval."""
|
||||
p = pred.permute(0, 2, 1, 3, 4).flatten(0, 1).float()
|
||||
x = target.permute(0, 2, 1, 3, 4).flatten(0, 1).float()
|
||||
rows = []
|
||||
for start in range(0, len(p), self.feature_batch_size):
|
||||
a, b = (
|
||||
p[start : start + self.feature_batch_size],
|
||||
x[start : start + self.feature_batch_size],
|
||||
)
|
||||
lp = self.lpips(a * 2 - 1, b * 2 - 1).float().flatten()
|
||||
with torch.autocast(p.device.type, enabled=False):
|
||||
q = torch.arange(11, device=p.device, dtype=torch.float32) - 5
|
||||
kernel = torch.exp(-q.square() / (2 * 1.5**2))
|
||||
kernel /= kernel.sum()
|
||||
window = (kernel[:, None] * kernel[None, :])[None, None].expand(3, 1, 11, 11)
|
||||
mu_a, mu_b = (F.conv2d(v, window, groups=3) for v in (a, b))
|
||||
va = (F.conv2d(a.square(), window, groups=3) - mu_a.square()).clamp_min(0)
|
||||
vb = (F.conv2d(b.square(), window, groups=3) - mu_b.square()).clamp_min(0)
|
||||
cov = F.conv2d(a * b, window, groups=3) - mu_a * mu_b
|
||||
ss = ((2 * mu_a * mu_b + 0.01**2) * (2 * cov + 0.03**2)) / (
|
||||
(mu_a.square() + mu_b.square() + 0.01**2) * (va + vb + 0.03**2)
|
||||
)
|
||||
psnr = -10 * (a - b).square().mean((1, 2, 3)).clamp_min(1e-12).log10()
|
||||
grid = grid_loss_per_frame(a, b, self.grid_period)
|
||||
rows.append(torch.stack((psnr, ss.mean((1, 2, 3)), lp, grid), dim=1))
|
||||
return dict(zip(("psnr", "ssim", "lpips", "grid"), torch.cat(rows).mean(0).tolist()))
|
||||
|
||||
@torch.no_grad()
|
||||
def log_validation(vae, tae, args, accelerator, weight_dtype, global_step,
|
||||
criterion, validation_dataloader, selection_state, ema_tae=None):
|
||||
"""Validate the Super decoder using official latents, then select inference weights."""
|
||||
accelerator.wait_for_everyone()
|
||||
if accelerator.is_main_process:
|
||||
logger.info("Running validation...")
|
||||
unwrapped_tae = accelerator.unwrap_model(tae)
|
||||
trainable_params = list(unwrapped_tae.model.decoder.parameters())
|
||||
was_training = unwrapped_tae.training
|
||||
if ema_tae is not None:
|
||||
ema_tae.store(trainable_params)
|
||||
ema_tae.copy_to(trainable_params)
|
||||
unwrapped_tae.eval()
|
||||
rows = []
|
||||
try:
|
||||
for i, batch in enumerate(validation_dataloader):
|
||||
if args.validation_max_samples and i >= args.validation_max_samples:
|
||||
break
|
||||
pixel_values = batch["pixel_values"].to(accelerator.device)
|
||||
with accelerator.autocast():
|
||||
gt_latents = vae.encode(pixel_values.to(weight_dtype)).latent_dist.mode()
|
||||
decoded = unwrapped_tae(gt_latents).sample[:, :, :pixel_values.shape[2]]
|
||||
decoded = decoded.float().add(1).mul(0.5)
|
||||
target = pixel_values.float().add(1).mul(0.5)
|
||||
rows.append(criterion.metrics(decoded, target))
|
||||
if i == 0:
|
||||
save_videos_grid(
|
||||
torch.cat([target, decoded]),
|
||||
os.path.join(args.output_dir, f"sample/step-{global_step}.mp4"), n_rows=2,
|
||||
)
|
||||
if not rows:
|
||||
raise ValueError("Validation set is empty")
|
||||
metrics = {key: sum(row[key] for row in rows) / len(rows) for key in rows[0]}
|
||||
if selection_state["reference"] is None:
|
||||
selection_state["reference"] = metrics
|
||||
reference = selection_state["reference"]
|
||||
eligible = (metrics["lpips"] <= reference["lpips"] * args.selection_lpips_ratio and
|
||||
metrics["grid"] <= reference["grid"] * args.selection_grid_ratio)
|
||||
best = eligible and (selection_state["best_psnr"] is None or metrics["psnr"] > selection_state["best_psnr"])
|
||||
if best:
|
||||
selection_state["best_psnr"] = metrics["psnr"]
|
||||
unwrapped_tae.save_pretrained(os.path.join(args.output_dir, "best", "taehv"))
|
||||
row = {"step": global_step, "weights": "ema" if ema_tae is not None else "raw",
|
||||
"eligible": eligible, "best": best, **metrics}
|
||||
with open(os.path.join(args.output_dir, "validation.jsonl"), "a") as file:
|
||||
file.write(json.dumps(row) + "\n")
|
||||
accelerator.log({f"validation/{key}": value for key, value in metrics.items()}, step=global_step)
|
||||
logger.info(row)
|
||||
finally:
|
||||
if ema_tae is not None:
|
||||
ema_tae.restore(trainable_params)
|
||||
unwrapped_tae.train(was_training)
|
||||
accelerator.wait_for_everyone()
|
||||
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Training script for the Wan2.2 Super decoder.")
|
||||
parser.add_argument(
|
||||
"--config_path",
|
||||
type=str,
|
||||
default="config/wan2.2/wan_civitai_5b.yaml",
|
||||
help="Path to the Wan2.2 model config yaml; vae_kwargs configures the frozen official VAE.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pretrained_model_name_or_path",
|
||||
type=str,
|
||||
default=None,
|
||||
required=True,
|
||||
help="Path to pretrained model directory (contains the full VAE weights).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--train_data_dir",
|
||||
type=str,
|
||||
default="datasets/X-Fun-Videos-Demo",
|
||||
help=("A folder containing the training data."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--train_data_meta",
|
||||
type=str,
|
||||
default="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json",
|
||||
help=("A JSON array containing the training video metadata."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output_dir",
|
||||
type=str,
|
||||
default="output_dir_taehv_super",
|
||||
help="The output directory where the model predictions and checkpoints will be written.",
|
||||
)
|
||||
parser.add_argument("--seed", type=int, default=42, help="A seed for reproducible training.")
|
||||
parser.add_argument(
|
||||
"--train_batch_size", type=int, default=1, help="Batch size (per device) for the training dataloader."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vae_mini_batch", type=int, default=1, help="Mini batch size for VAE encoding."
|
||||
)
|
||||
parser.add_argument("--num_train_epochs", type=int, default=100)
|
||||
parser.add_argument(
|
||||
"--max_train_steps",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Total number of training steps to perform. If provided, overrides num_train_epochs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gradient_accumulation_steps",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of updates steps to accumulate before performing a backward/update pass.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--learning_rate",
|
||||
type=float,
|
||||
default=5e-6,
|
||||
help="Initial learning rate (after the potential warmup period) to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lr_scheduler",
|
||||
type=str,
|
||||
default="cosine",
|
||||
help=(
|
||||
'The scheduler type to use. Choose between ["linear", "cosine", "cosine_with_restarts", "polynomial",'
|
||||
' "constant", "constant_with_warmup"]'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lr_warmup_steps", type=int, default=200, help="Number of steps for the warmup in the lr scheduler."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lr_min_ratio", type=float, default=0.2,
|
||||
help="Final LR / peak LR for the cosine scheduler (stage 4a: 1e-6 / 5e-6).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow_tf32",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Whether or not to allow TF32 on Ampere GPUs. Can be used to speed up training."
|
||||
),
|
||||
)
|
||||
parser.add_argument("--use_ema", action=argparse.BooleanOptionalAction, default=False, help="Whether to use EMA model.")
|
||||
parser.add_argument(
|
||||
"--dataloader_num_workers",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Number of subprocesses to use for data loading.",
|
||||
)
|
||||
parser.add_argument("--adam_beta1", type=float, default=0.9, help="The beta1 parameter for the Adam optimizer.")
|
||||
parser.add_argument("--adam_beta2", type=float, default=0.99, help="The beta2 parameter for the Adam optimizer.")
|
||||
parser.add_argument("--adam_weight_decay", type=float, default=1e-2, help="Weight decay to use.")
|
||||
parser.add_argument("--adam_epsilon", type=float, default=1e-08, help="Epsilon value for the Adam optimizer.")
|
||||
parser.add_argument("--max_grad_norm", default=1.0, type=float, help="Max gradient norm.")
|
||||
parser.add_argument(
|
||||
"--logging_dir",
|
||||
type=str,
|
||||
default="logs",
|
||||
help="TensorBoard log directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mixed_precision",
|
||||
type=str,
|
||||
default="bf16",
|
||||
choices=["no", "fp16", "bf16"],
|
||||
help="Whether to use mixed precision. Choose between fp16 and bf16.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--report_to",
|
||||
type=str,
|
||||
default="tensorboard",
|
||||
help='The integration to report the results and logs to.',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--checkpointing_steps",
|
||||
type=int,
|
||||
default=500,
|
||||
help="Save a checkpoint of the training state every X updates.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--checkpoints_total_limit",
|
||||
type=int,
|
||||
default=None,
|
||||
help=("Max number of checkpoints to store."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resume_from_checkpoint",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Whether training should be resumed from a previous checkpoint.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validation_steps",
|
||||
type=int,
|
||||
default=2000,
|
||||
help="Run validation every X steps.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tracker_project_name",
|
||||
type=str,
|
||||
default="taehv-train",
|
||||
help="The `project_name` argument passed to Accelerator.init_trackers.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--video_sample_size",
|
||||
type=int,
|
||||
default=512,
|
||||
help="Training video sample size.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--video_sample_stride",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Sample stride of the video.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--video_sample_n_frames",
|
||||
type=int,
|
||||
default=25,
|
||||
help="Num frame of video. Must be 4k+1 to match the causal temporal compression.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--tae_path",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Optional Super weights to warm-start from (taew2_2_super.pth/.safetensors, a directory, "
|
||||
"or a previous checkpoint-xxx/taehv dir). If not given, train from scratch.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use_taehv_sequential", action="store_true",
|
||||
help="Use the sequential TAE decoder instead of the parallel path.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--from_scratch",
|
||||
action="store_true",
|
||||
help="Initialize a new Super decoder without student weights.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fix_sample_size",
|
||||
nargs=2,
|
||||
type=int,
|
||||
default=None,
|
||||
help="Fixed [height, width] overriding video_sample_size.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--video_sample_strides",
|
||||
nargs="+",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Optional per-video random sampling strides.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validation_data_meta",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Held-out validation JSON index.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validation_n_frames",
|
||||
type=int,
|
||||
default=81,
|
||||
help="Validation clip length, 4k+1.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--validation_max_samples",
|
||||
type=int,
|
||||
default=0,
|
||||
help="Validation sample cap; zero evaluates the complete index.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dino_repo",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Optional local pinned DINOv2 source directory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dino_weights",
|
||||
type=str,
|
||||
default=None,
|
||||
help="Optional local ViT-B/14 weights.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--feature_batch_size",
|
||||
type=int,
|
||||
default=13,
|
||||
help="Frame batch size for perceptual networks.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--feature_checkpointing",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=False,
|
||||
help="Checkpoint perceptual feature computation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--grid_period",
|
||||
type=int,
|
||||
default=16,
|
||||
help="Spatial residual period for grid loss.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--l1_loss_weight",
|
||||
type=float,
|
||||
default=0.1,
|
||||
help="RGB L1 reconstruction loss weight.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--lpips_loss_weight",
|
||||
type=float,
|
||||
default=0.1,
|
||||
help="LPIPS reconstruction loss weight.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dino_loss_weight",
|
||||
type=float,
|
||||
default=0.02,
|
||||
help="DINOv2 patch feature loss weight.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--grid_loss_weight",
|
||||
type=float,
|
||||
default=1.0,
|
||||
help="Periodic spatial residual loss weight.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ema_decay",
|
||||
type=float,
|
||||
default=0.999,
|
||||
help="EMA decay for decoder parameters.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--selection_lpips_ratio",
|
||||
type=float,
|
||||
default=1.02,
|
||||
help="Maximum LPIPS relative to the first validation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--selection_grid_ratio",
|
||||
type=float,
|
||||
default=1.05,
|
||||
help="Maximum grid loss relative to the first validation.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.fix_sample_size is not None:
|
||||
args.video_sample_height, args.video_sample_width = args.fix_sample_size
|
||||
else:
|
||||
args.video_sample_height = args.video_sample_width = args.video_sample_size
|
||||
if any(size < 32 or size % 16 for size in (args.video_sample_height, args.video_sample_width)):
|
||||
parser.error("Video dimensions must be >=32 and divisible by 16")
|
||||
if any(frames < 1 or (frames - 1) % 4 for frames in (args.video_sample_n_frames, args.validation_n_frames)):
|
||||
parser.error("Video frame counts must be 4k+1")
|
||||
if args.from_scratch and (args.tae_path or args.resume_from_checkpoint):
|
||||
parser.error("--from_scratch cannot be combined with weight initialization or resume")
|
||||
if args.max_train_steps is not None and args.max_train_steps < 1:
|
||||
parser.error("max_train_steps must be positive")
|
||||
if min(args.num_train_epochs, args.train_batch_size, args.vae_mini_batch,
|
||||
args.gradient_accumulation_steps, args.checkpointing_steps, args.video_sample_stride,
|
||||
args.feature_batch_size, args.grid_period) < 1:
|
||||
parser.error("Epochs, batch sizes, checkpoint interval, stride and grid period must be positive")
|
||||
if min(args.dataloader_num_workers, args.validation_steps, args.validation_max_samples, args.lr_warmup_steps) < 0:
|
||||
parser.error("Workers, validation intervals/sample cap and warmup cannot be negative")
|
||||
if args.video_sample_strides is not None and (not args.video_sample_strides or min(args.video_sample_strides) < 1):
|
||||
parser.error("video_sample_strides must be positive")
|
||||
if args.checkpoints_total_limit is not None and args.checkpoints_total_limit < 1:
|
||||
parser.error("checkpoints_total_limit must be positive")
|
||||
if not 0 <= args.ema_decay < 1 or not 0 <= args.adam_beta1 < 1 or not 0 <= args.adam_beta2 < 1:
|
||||
parser.error("Invalid EMA decay or Adam betas")
|
||||
if args.adam_weight_decay < 0:
|
||||
parser.error("Adam weight decay cannot be negative")
|
||||
if not 0 <= args.lr_min_ratio <= 1:
|
||||
parser.error("lr_min_ratio must be between 0 and 1")
|
||||
if any(not math.isfinite(value) or value <= 0 for value in
|
||||
(args.learning_rate, args.adam_epsilon, args.max_grad_norm,
|
||||
args.selection_lpips_ratio, args.selection_grid_ratio)):
|
||||
parser.error("Learning rate, epsilon, gradient norm and selection ratios must be finite and positive")
|
||||
loss_weights = (args.l1_loss_weight, args.lpips_loss_weight, args.dino_loss_weight,
|
||||
args.grid_loss_weight)
|
||||
if not any(loss_weights) or any(not math.isfinite(value) or value < 0 for value in loss_weights):
|
||||
parser.error("Loss weights must be finite and nonnegative, with at least one positive term")
|
||||
return args
|
||||
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
logging_dir = os.path.join(args.output_dir, args.logging_dir)
|
||||
accelerator_project_config = ProjectConfiguration(project_dir=args.output_dir, logging_dir=logging_dir)
|
||||
accelerator = Accelerator(
|
||||
gradient_accumulation_steps=args.gradient_accumulation_steps,
|
||||
mixed_precision=args.mixed_precision,
|
||||
log_with=None if args.report_to == "none" else args.report_to,
|
||||
project_config=accelerator_project_config,
|
||||
)
|
||||
if accelerator.distributed_type.name not in ("NO", "MULTI_GPU", "MULTI_CPU"):
|
||||
raise ValueError("This decoder trainer supports single device and ordinary DDP")
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||
datefmt="%m/%d/%Y %H:%M:%S",
|
||||
level=logging.INFO,
|
||||
)
|
||||
logger.info(accelerator.state, main_process_only=False)
|
||||
if accelerator.is_local_main_process:
|
||||
diffusers.utils.logging.set_verbosity_info()
|
||||
else:
|
||||
diffusers.utils.logging.set_verbosity_error()
|
||||
set_seed(args.seed)
|
||||
if args.allow_tf32:
|
||||
torch.backends.cuda.matmul.allow_tf32 = True
|
||||
if accelerator.is_main_process:
|
||||
os.makedirs(args.output_dir, exist_ok=True)
|
||||
if not args.resume_from_checkpoint and (
|
||||
os.path.exists(os.path.join(args.output_dir, "train.jsonl")) or
|
||||
any(name.startswith("checkpoint-") for name in os.listdir(args.output_dir))
|
||||
):
|
||||
raise FileExistsError("Use a new output_dir or --resume_from_checkpoint")
|
||||
|
||||
# For mixed precision training, keep trainable parameters and Adam moments FP32.
|
||||
weight_dtype = torch.float32
|
||||
if accelerator.mixed_precision == "fp16":
|
||||
weight_dtype = torch.float16
|
||||
elif accelerator.mixed_precision == "bf16":
|
||||
weight_dtype = torch.bfloat16
|
||||
|
||||
# ==================== Load Config ====================
|
||||
config = OmegaConf.load(args.config_path)
|
||||
if config['vae_kwargs'].get('vae_type') != 'AutoencoderKLWan3_8':
|
||||
raise ValueError("Super training requires the official 48-channel Wan2.2 VAE")
|
||||
|
||||
# ==================== Load Models ====================
|
||||
vae = AutoencoderKLWan3_8.from_pretrained(
|
||||
os.path.join(args.pretrained_model_name_or_path, config['vae_kwargs'].get('vae_subpath', 'vae')),
|
||||
additional_kwargs=OmegaConf.to_container(config['vae_kwargs']),
|
||||
)
|
||||
vae.eval()
|
||||
vae.requires_grad_(False)
|
||||
vae.to(accelerator.device, dtype=weight_dtype)
|
||||
|
||||
tae_load_kwargs = {"latent_channels": 48, "patch_size": 2, "arch_variant": "super"}
|
||||
if args.tae_path is not None and not args.resume_from_checkpoint:
|
||||
logger.info(f"Warm-starting Super TAE from checkpoint: {args.tae_path}")
|
||||
tae = AutoencoderTinyWan.from_pretrained(
|
||||
args.tae_path, additional_kwargs=tae_load_kwargs,
|
||||
)
|
||||
else:
|
||||
logger.info("Initializing Super TAE (training state is restored below when resuming)")
|
||||
tae = AutoencoderTinyWan(**tae_load_kwargs)
|
||||
if tae.config.arch_variant != "super" or tae.config.latent_channels != 48 or tae.config.patch_size != 2:
|
||||
raise ValueError("Expected a 48-channel, patch-size-2 Super checkpoint")
|
||||
tae.parallel = not args.use_taehv_sequential
|
||||
tae.float()
|
||||
tae.requires_grad_(False)
|
||||
tae.model.decoder.requires_grad_(True)
|
||||
tae.train()
|
||||
|
||||
trainable_params = list(filter(lambda p: p.requires_grad, tae.parameters()))
|
||||
total_params = sum(p.numel() for p in tae.parameters())
|
||||
trainable_count = sum(p.numel() for p in trainable_params)
|
||||
logger.info(f"Total params: {total_params / 1e6:.2f}M, Trainable params: {trainable_count / 1e6:.2f}M")
|
||||
ema_tae = None
|
||||
if args.use_ema:
|
||||
ema_tae = EMAModel(
|
||||
trainable_params, decay=args.ema_decay, min_decay=args.ema_decay, use_ema_warmup=False,
|
||||
)
|
||||
|
||||
# ==================== Save/Load Hooks ====================
|
||||
def save_model_hook(models, weights, output_dir):
|
||||
if accelerator.is_main_process:
|
||||
accelerator.unwrap_model(models[0]).save_pretrained(os.path.join(output_dir, "taehv"))
|
||||
trainer_state = {"step": global_step, "epoch": epoch, "batch": resume_step, **selection_state}
|
||||
with open(os.path.join(output_dir, "trainer_state.json"), "w") as file:
|
||||
json.dump(trainer_state, file, indent=2)
|
||||
# The model is saved in Diffusers format by this hook, as in train_taehv.py.
|
||||
weights.pop()
|
||||
|
||||
def load_model_hook(models, input_dir):
|
||||
while models:
|
||||
model = accelerator.unwrap_model(models.pop())
|
||||
load_model = AutoencoderTinyWan.from_pretrained(input_dir, subfolder="taehv")
|
||||
model.register_to_config(**load_model.config)
|
||||
model.load_state_dict(load_model.state_dict(), strict=True)
|
||||
del load_model
|
||||
|
||||
accelerator.register_save_state_pre_hook(save_model_hook)
|
||||
accelerator.register_load_state_pre_hook(load_model_hook)
|
||||
|
||||
# ==================== Optimizer ====================
|
||||
optimizer = torch.optim.AdamW(
|
||||
trainable_params, lr=args.learning_rate,
|
||||
betas=(args.adam_beta1, args.adam_beta2),
|
||||
weight_decay=args.adam_weight_decay, eps=args.adam_epsilon,
|
||||
)
|
||||
|
||||
# ==================== Dataset ====================
|
||||
train_dataset = VideoDataset(
|
||||
args.train_data_meta, args.train_data_dir,
|
||||
sample_size=args.video_sample_size,
|
||||
sample_stride=args.video_sample_strides or args.video_sample_stride,
|
||||
sample_n_frames=args.video_sample_n_frames,
|
||||
enable_bucket=True, enable_inpaint=False, text_drop_ratio=0,
|
||||
)
|
||||
if not len(train_dataset):
|
||||
raise ValueError("Training set is empty")
|
||||
|
||||
def collate_fn(examples):
|
||||
"""Fixed canvas, aspect-preserving resize/crop; align the shortest clip to 4k+1."""
|
||||
frames = (min(len(e["pixel_values"]) for e in examples) - 1) // 4 * 4 + 1
|
||||
height, width = args.video_sample_height, args.video_sample_width
|
||||
videos = []
|
||||
for example in examples:
|
||||
x = torch.from_numpy(example["pixel_values"][:frames]).permute(0, 3, 1, 2).float()
|
||||
h, w = x.shape[-2:]
|
||||
scale = max(height / h, width / w)
|
||||
resize_size = (math.ceil(h * scale), math.ceil(w * scale))
|
||||
x = transforms.functional.resize(x, resize_size, antialias=True)
|
||||
x = transforms.functional.center_crop(x, (height, width))
|
||||
videos.append(x.div_(127.5).sub_(1))
|
||||
return {"pixel_values": torch.stack(videos).permute(0, 2, 1, 3, 4).contiguous()}
|
||||
|
||||
batch_sampler_generator = torch.Generator().manual_seed(args.seed)
|
||||
batch_sampler = ImageVideoSampler(
|
||||
RandomSampler(train_dataset, generator=batch_sampler_generator),
|
||||
train_dataset, args.train_batch_size, drop_last=True,
|
||||
)
|
||||
train_dataloader = torch.utils.data.DataLoader(
|
||||
train_dataset,
|
||||
batch_sampler=batch_sampler,
|
||||
collate_fn=collate_fn,
|
||||
persistent_workers=True if args.dataloader_num_workers != 0 else False,
|
||||
num_workers=args.dataloader_num_workers,
|
||||
)
|
||||
validation_dataloader = None
|
||||
if args.validation_data_meta is not None:
|
||||
validation_dataset = VideoDataset(
|
||||
args.validation_data_meta, args.train_data_dir,
|
||||
sample_n_frames=args.validation_n_frames, sample_stride=1,
|
||||
enable_bucket=True, enable_inpaint=False, text_drop_ratio=0,
|
||||
random_start=False,
|
||||
)
|
||||
validation_dataloader = torch.utils.data.DataLoader(
|
||||
validation_dataset, batch_size=1, collate_fn=collate_fn,
|
||||
)
|
||||
|
||||
# ==================== LR Scheduler ====================
|
||||
tae, optimizer, train_dataloader = accelerator.prepare(tae, optimizer, train_dataloader)
|
||||
num_update_steps_per_epoch = math.ceil(len(train_dataloader) / args.gradient_accumulation_steps)
|
||||
if num_update_steps_per_epoch == 0:
|
||||
raise ValueError("Training data does not contain a complete batch")
|
||||
if args.max_train_steps is None:
|
||||
args.max_train_steps = args.num_train_epochs * num_update_steps_per_epoch
|
||||
args.num_train_epochs = math.ceil(args.max_train_steps / num_update_steps_per_epoch)
|
||||
lr_scheduler = get_scheduler(
|
||||
args.lr_scheduler, optimizer=optimizer.optimizer,
|
||||
num_warmup_steps=args.lr_warmup_steps,
|
||||
num_training_steps=args.max_train_steps,
|
||||
)
|
||||
if args.lr_scheduler == "cosine":
|
||||
# Keep Diffusers warmup/cosine and add stage 4a's nonzero final LR.
|
||||
cosine_schedule = lr_scheduler.lr_lambdas[0]
|
||||
|
||||
def lr_lambda(step):
|
||||
factor = cosine_schedule(step)
|
||||
if step < args.lr_warmup_steps:
|
||||
return factor
|
||||
return args.lr_min_ratio + (1 - args.lr_min_ratio) * factor
|
||||
|
||||
lr_scheduler.lr_lambdas = [lr_lambda] * len(lr_scheduler.lr_lambdas)
|
||||
# Register explicitly so it advances once per optimizer update, at any world size.
|
||||
accelerator.register_for_checkpointing(lr_scheduler)
|
||||
if args.use_ema:
|
||||
ema_tae.to(accelerator.device)
|
||||
accelerator.register_for_checkpointing(ema_tae)
|
||||
|
||||
# ==================== TinyVAE Losses ====================
|
||||
loss_weights = {
|
||||
"l1": args.l1_loss_weight,
|
||||
"lpips": args.lpips_loss_weight,
|
||||
"dino": args.dino_loss_weight,
|
||||
"grid": args.grid_loss_weight,
|
||||
}
|
||||
criterion = TinyVAEReconstructionLoss(
|
||||
loss_weights,
|
||||
dino_repo=args.dino_repo, dino_weights=args.dino_weights,
|
||||
feature_batch_size=args.feature_batch_size,
|
||||
feature_checkpointing=args.feature_checkpointing, grid_period=args.grid_period,
|
||||
).to(accelerator.device)
|
||||
|
||||
if accelerator.is_main_process:
|
||||
tracker_config = dict(vars(args))
|
||||
keys_to_pop = [key for key, value in tracker_config.items() if isinstance(value, list)]
|
||||
for key in keys_to_pop:
|
||||
tracker_config.pop(key)
|
||||
accelerator.init_trackers(args.tracker_project_name, tracker_config)
|
||||
|
||||
# ==================== Training ====================
|
||||
total_batch_size = args.train_batch_size * accelerator.num_processes * args.gradient_accumulation_steps
|
||||
logger.info("***** Running Super decoder training *****")
|
||||
logger.info(f" Num examples = {len(train_dataset)}")
|
||||
logger.info(f" Num Epochs = {args.num_train_epochs}")
|
||||
logger.info(f" Instantaneous batch size per device = {args.train_batch_size}")
|
||||
logger.info(f" Total train batch size (w. parallel, distributed & accumulation) = {total_batch_size}")
|
||||
logger.info(f" Gradient Accumulation steps = {args.gradient_accumulation_steps}")
|
||||
logger.info(f" Total optimization steps = {args.max_train_steps}")
|
||||
logger.info(f" Loss weights = {loss_weights}")
|
||||
global_step = 0
|
||||
first_epoch = 0
|
||||
resume_step = 0
|
||||
selection_state = {"reference": None, "best_psnr": None}
|
||||
|
||||
# Resume from checkpoint
|
||||
if args.resume_from_checkpoint:
|
||||
if args.resume_from_checkpoint != "latest":
|
||||
path = args.resume_from_checkpoint
|
||||
else:
|
||||
dirs = [name for name in os.listdir(args.output_dir)
|
||||
if name.startswith("checkpoint-") and name.split("-")[-1].isdigit()]
|
||||
dirs = sorted(dirs, key=lambda name: int(name.split("-")[-1]))
|
||||
if not dirs:
|
||||
raise FileNotFoundError(f"No checkpoints in {args.output_dir}")
|
||||
path = os.path.join(args.output_dir, dirs[-1])
|
||||
with open(os.path.join(path, "trainer_state.json")) as file:
|
||||
trainer_state = json.load(file)
|
||||
global_step = trainer_state["step"]
|
||||
first_epoch = trainer_state["epoch"]
|
||||
resume_step = trainer_state["batch"]
|
||||
selection_state["reference"] = trainer_state["reference"]
|
||||
selection_state["best_psnr"] = trainer_state["best_psnr"]
|
||||
accelerator.print(f"Resuming from checkpoint {path}")
|
||||
accelerator.load_state(path)
|
||||
if args.use_ema:
|
||||
ema_tae.to(accelerator.device)
|
||||
initial_global_step = global_step
|
||||
epoch = first_epoch
|
||||
if accelerator.is_main_process:
|
||||
OmegaConf.save(OmegaConf.create(vars(args)), os.path.join(args.output_dir, f"args-step-{global_step}.yaml"))
|
||||
if validation_dataloader is not None and selection_state["reference"] is None:
|
||||
log_validation(vae, tae, args, accelerator, weight_dtype, global_step,
|
||||
criterion, validation_dataloader, selection_state, ema_tae)
|
||||
|
||||
progress_bar = PauseAwareTqdm(
|
||||
range(0, args.max_train_steps), initial=initial_global_step, desc="Steps",
|
||||
disable=not accelerator.is_local_main_process,
|
||||
)
|
||||
optimizer.zero_grad()
|
||||
for epoch in range(first_epoch, args.num_train_epochs):
|
||||
if global_step >= args.max_train_steps:
|
||||
break
|
||||
train_loss = torch.zeros(1 + len(loss_weights), device=accelerator.device)
|
||||
micro_steps = 0
|
||||
train_dataloader.set_epoch(epoch)
|
||||
batch_sampler.sampler.generator = torch.Generator().manual_seed(args.seed + epoch)
|
||||
batch_sampler.sampler._pos_start = 0
|
||||
batch_sampler.bucket = {"image": [], "video": []}
|
||||
epoch_dataloader = accelerator.skip_first_batches(train_dataloader, resume_step) if resume_step else train_dataloader
|
||||
for step, batch in enumerate(epoch_dataloader, start=resume_step):
|
||||
with accelerator.accumulate(tae):
|
||||
pixel_values = batch["pixel_values"].to(accelerator.device) # [B, C, F, H, W], FP32 target
|
||||
bsz = pixel_values.shape[0]
|
||||
|
||||
# 1. Official latents, frozen.
|
||||
with torch.no_grad(), accelerator.autocast():
|
||||
gt_latents_list = []
|
||||
for i in range(0, bsz, args.vae_mini_batch):
|
||||
encoded = vae.encode(pixel_values[i:i + args.vae_mini_batch].to(weight_dtype)).latent_dist.mode()
|
||||
gt_latents_list.append(encoded)
|
||||
gt_latents = torch.cat(gt_latents_list, dim=0).detach()
|
||||
|
||||
# 2. Student forward: official latent -> Super decoder, through DDP.
|
||||
with accelerator.autocast():
|
||||
decoded = tae(gt_latents).sample[:, :, :pixel_values.shape[2]]
|
||||
decoded = decoded.float().add(1).mul(0.5)
|
||||
target = pixel_values.float().add(1).mul(0.5)
|
||||
loss, loss_terms = criterion(decoded, target)
|
||||
if not torch.isfinite(loss):
|
||||
raise FloatingPointError(f"Nonfinite loss at step {global_step}")
|
||||
|
||||
# 3. Gather losses for logging and backpropagate.
|
||||
train_loss += torch.stack([loss.detach(), *[loss_terms[key].detach() for key in loss_weights]])
|
||||
micro_steps += 1
|
||||
accelerator.backward(loss)
|
||||
if accelerator.sync_gradients:
|
||||
accelerator.clip_grad_norm_(trainable_params, args.max_grad_norm)
|
||||
optimizer.step()
|
||||
optimizer.zero_grad()
|
||||
if accelerator.sync_gradients and not accelerator.optimizer_step_was_skipped:
|
||||
lr_scheduler.step()
|
||||
if args.use_ema:
|
||||
ema_tae.step(trainable_params)
|
||||
|
||||
resume_step = step + 1
|
||||
if accelerator.sync_gradients and not accelerator.optimizer_step_was_skipped:
|
||||
progress_bar.update(1)
|
||||
global_step += 1
|
||||
averaged_loss = accelerator.reduce(train_loss / micro_steps, reduction="mean").tolist()
|
||||
logs = dict(zip(("loss", *loss_weights), averaged_loss))
|
||||
logs.update(step=global_step, frames=pixel_values.shape[2], lr=lr_scheduler.get_last_lr()[0])
|
||||
accelerator.log(logs, step=global_step)
|
||||
if accelerator.is_main_process:
|
||||
with open(os.path.join(args.output_dir, "train.jsonl"), "a") as file:
|
||||
file.write(json.dumps(logs) + "\n")
|
||||
train_loss.zero_()
|
||||
micro_steps = 0
|
||||
progress_bar.set_postfix(step_loss=logs["loss"], lr=logs["lr"])
|
||||
|
||||
# Validation
|
||||
if validation_dataloader is not None and args.validation_steps and global_step % args.validation_steps == 0:
|
||||
with progress_bar.paused():
|
||||
log_validation(vae, tae, args, accelerator, weight_dtype, global_step,
|
||||
criterion, validation_dataloader, selection_state, ema_tae)
|
||||
|
||||
# Checkpointing
|
||||
if global_step % args.checkpointing_steps == 0:
|
||||
if accelerator.is_main_process and args.checkpoints_total_limit is not None:
|
||||
checkpoints = [name for name in os.listdir(args.output_dir)
|
||||
if name.startswith("checkpoint-") and name.split("-")[-1].isdigit()]
|
||||
checkpoints = sorted(checkpoints, key=lambda name: int(name.split("-")[-1]))
|
||||
num_to_remove = len(checkpoints) - args.checkpoints_total_limit + 1
|
||||
for removing_checkpoint in checkpoints[:max(num_to_remove, 0)]:
|
||||
shutil.rmtree(os.path.join(args.output_dir, removing_checkpoint))
|
||||
accelerator.wait_for_everyone()
|
||||
save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")
|
||||
with progress_bar.paused():
|
||||
accelerator.save_state(save_path)
|
||||
logger.info(f"Saved state to {save_path}")
|
||||
if global_step >= args.max_train_steps:
|
||||
break
|
||||
if global_step >= args.max_train_steps:
|
||||
break
|
||||
resume_step = 0
|
||||
progress_bar.close()
|
||||
|
||||
# Final validation and save. Restore raw parameters after EMA validation.
|
||||
if validation_dataloader is not None and (not args.validation_steps or global_step % args.validation_steps):
|
||||
log_validation(vae, tae, args, accelerator, weight_dtype, global_step,
|
||||
criterion, validation_dataloader, selection_state, ema_tae)
|
||||
accelerator.wait_for_everyone()
|
||||
save_path = os.path.join(args.output_dir, f"checkpoint-{global_step}")
|
||||
accelerator.save_state(save_path)
|
||||
logger.info(f"Saved state to {save_path}")
|
||||
accelerator.end_training()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.2-TI2V-5B"
|
||||
export DATASET_NAME="${DATASET_NAME:-datasets/X-Fun-Videos-Demo}"
|
||||
export DATASET_META_NAME="${DATASET_META_NAME:-$DATASET_NAME/metadata_add_width_height.json}"
|
||||
export TAE_PATH="models/Diffusion_Transformer/taew2_2_super.pth"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
# export NCCL_IB_DISABLE=1
|
||||
# export NCCL_P2P_DISABLE=1
|
||||
NCCL_DEBUG=INFO
|
||||
|
||||
accelerate launch --mixed_precision="bf16" scripts/taehv/train_taehv_super.py \
|
||||
--config_path="config/wan2.2/wan_civitai_5b.yaml" \
|
||||
--pretrained_model_name_or_path="$MODEL_NAME" \
|
||||
--tae_path="$TAE_PATH" \
|
||||
--train_data_dir="$DATASET_NAME" \
|
||||
--train_data_meta="$DATASET_META_NAME" \
|
||||
--fix_sample_size 720 1280 \
|
||||
--video_sample_strides 1 2 3 \
|
||||
--video_sample_n_frames=25 \
|
||||
--validation_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--max_train_steps=10000 \
|
||||
--checkpointing_steps=1000 \
|
||||
--validation_steps=1000 \
|
||||
--validation_max_samples=0 \
|
||||
--learning_rate=5e-06 \
|
||||
--lr_scheduler="cosine" \
|
||||
--lr_warmup_steps=200 \
|
||||
--lr_min_ratio=0.2 \
|
||||
--l1_loss_weight=0.1 \
|
||||
--lpips_loss_weight=0.1 \
|
||||
--dino_loss_weight=0.02 \
|
||||
--grid_loss_weight=1.0 \
|
||||
--grid_period=16 \
|
||||
--feature_batch_size=13 \
|
||||
--use_ema \
|
||||
--ema_decay=0.999 \
|
||||
--selection_lpips_ratio=1.02 \
|
||||
--selection_grid_ratio=1.05 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_taehv_w2.2_super" \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_beta1=0.9 \
|
||||
--adam_beta2=0.99 \
|
||||
--adam_weight_decay=1e-2 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=1.0
|
||||
@@ -173,6 +173,7 @@ class VideoDataset(Dataset):
|
||||
video_length_drop_start=0.0,
|
||||
video_length_drop_end=1.0,
|
||||
text_drop_ratio=0.1,
|
||||
random_start=True,
|
||||
):
|
||||
# Loading annotations from files
|
||||
print(f"loading annotations from {ann_path} ...")
|
||||
@@ -189,6 +190,7 @@ class VideoDataset(Dataset):
|
||||
self.video_length_drop_start = video_length_drop_start
|
||||
self.video_length_drop_end = video_length_drop_end
|
||||
self.text_drop_ratio = text_drop_ratio
|
||||
self.random_start = random_start
|
||||
|
||||
sample_size = tuple(sample_size) if not isinstance(sample_size, int) else (sample_size, sample_size)
|
||||
self.pixel_transforms = transforms.Compose(
|
||||
@@ -212,17 +214,22 @@ class VideoDataset(Dataset):
|
||||
|
||||
with VideoReader_contextmanager(video_dir, num_threads=2) as video_reader:
|
||||
# Calculate frame sampling range with length dropout
|
||||
sample_stride = (random.choice(self.sample_stride)
|
||||
if isinstance(self.sample_stride, (tuple, list)) else self.sample_stride)
|
||||
min_sample_n_frames = min(
|
||||
self.sample_n_frames,
|
||||
int(len(video_reader) * (self.video_length_drop_end - self.video_length_drop_start) // self.sample_stride)
|
||||
int((len(video_reader) * (self.video_length_drop_end - self.video_length_drop_start) - 1)
|
||||
// sample_stride) + 1
|
||||
)
|
||||
if min_sample_n_frames == 0:
|
||||
raise ValueError(f"No Frames in video.")
|
||||
|
||||
# Select contiguous clip with random start position
|
||||
video_length = int(self.video_length_drop_end * len(video_reader))
|
||||
clip_length = min(video_length, (min_sample_n_frames - 1) * self.sample_stride + 1)
|
||||
start_idx = random.randint(int(self.video_length_drop_start * video_length), video_length - clip_length) if video_length != clip_length else 0
|
||||
clip_length = min(video_length, (min_sample_n_frames - 1) * sample_stride + 1)
|
||||
start_idx = int(self.video_length_drop_start * video_length)
|
||||
if self.random_start and video_length != clip_length:
|
||||
start_idx = random.randint(start_idx, video_length - clip_length)
|
||||
batch_index = np.linspace(start_idx, start_idx + clip_length - 1, min_sample_n_frames, dtype=int)
|
||||
|
||||
try:
|
||||
|
||||
@@ -449,6 +449,10 @@ class AutoencoderTinyWan(ModelMixin, ConfigMixin, FromOriginalModelMixin):
|
||||
return (decoded,)
|
||||
return DecoderOutput(sample=decoded)
|
||||
|
||||
def forward(self, z: torch.Tensor, return_dict: bool = True):
|
||||
# Use forward so decoder training runs through DDP hooks.
|
||||
return self.decode(z, return_dict=return_dict)
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, pretrained_model_path, additional_kwargs={}, **kwargs):
|
||||
def filter_kwargs(cls, kwargs):
|
||||
|
||||
Reference in New Issue
Block a user