Update one-forcing and causal forcing params
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from diffusers import FlowMatchEulerDiscreteScheduler
|
||||
from omegaconf import OmegaConf
|
||||
from PIL import Image
|
||||
|
||||
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.dist import set_multi_gpus_devices, shard_model
|
||||
from videox_fun.models import (AutoencoderKLWan, AutoTokenizer,
|
||||
WanT5EncoderModel,
|
||||
WanTransformer3DModel_SelfForcing)
|
||||
from videox_fun.pipeline import WanSelfForcingPipeline
|
||||
from videox_fun.utils import (FlowDPMSolverMultistepScheduler,
|
||||
FlowUniPCMultistepScheduler,
|
||||
apply_gpu_memory_mode, filter_kwargs, merge_lora,
|
||||
save_videos_grid, unmerge_lora)
|
||||
|
||||
# GPU memory mode, which can be chosen in [model_full_load, model_full_load_and_qfloat8, model_cpu_offload, model_cpu_offload_and_qfloat8, model_group_offload, sequential_cpu_offload].
|
||||
# model_full_load means that the entire model will be moved to the GPU.
|
||||
#
|
||||
# model_full_load_and_qfloat8 means that the entire model will be moved to the GPU,
|
||||
# and the transformer model has been quantized to float8, which can save more GPU memory.
|
||||
#
|
||||
# model_cpu_offload means that the entire model will be moved to the CPU after use, which can save some GPU memory.
|
||||
#
|
||||
# model_cpu_offload_and_qfloat8 indicates that the entire model will be moved to the CPU after use,
|
||||
# and the transformer model has been quantized to float8, which can save more GPU memory.
|
||||
#
|
||||
# model_group_offload transfers internal layer groups between CPU/CUDA,
|
||||
# balancing memory efficiency and speed between full-module and leaf-level offloading methods.
|
||||
#
|
||||
# sequential_cpu_offload means that each layer of the model will be moved to the CPU after use,
|
||||
# resulting in slower speeds but saving a large amount of GPU memory.
|
||||
GPU_memory_mode = "model_full_load"
|
||||
# Multi GPUs config
|
||||
# Please ensure that the product of ulysses_degree and ring_degree equals the number of GPUs used.
|
||||
# For example, if you are using 8 GPUs, you can set ulysses_degree = 2 and ring_degree = 4.
|
||||
# If you are using 1 GPU, you can set ulysses_degree = 1 and ring_degree = 1.
|
||||
ulysses_degree = 1
|
||||
ring_degree = 1
|
||||
# Use FSDP to save more GPU memory in multi gpus.
|
||||
fsdp_dit = False
|
||||
fsdp_text_encoder = True
|
||||
# Compile will give a speedup in fixed resolution and need a little GPU memory.
|
||||
# The compile_dit is not compatible with the fsdp_dit and sequential_cpu_offload.
|
||||
compile_dit = False
|
||||
|
||||
# Config and model path
|
||||
config_path = "config/wan2.1/wan_civitai.yaml"
|
||||
# One-Forcing distills a Wan2.1-T2V-1.3B student (the 14B model is only the DMD teacher at training time).
|
||||
model_name = "models/Diffusion_Transformer/Wan2.1-T2V-1.3B"
|
||||
|
||||
# Choose the sampler in "Flow", "Flow_Unipc", "Flow_DPM++".
|
||||
sampler_name = "Flow"
|
||||
# [NOTE]: Noise schedule shift parameter. Affects temporal dynamics.
|
||||
# Used when the sampler is in "Flow_Unipc", "Flow_DPM++".
|
||||
shift = 5.0
|
||||
# One-Forcing is a DMD-distilled student, so it uses stochastic sampling.
|
||||
stochastic_sampling = True
|
||||
|
||||
# One-Forcing checkpoint to overlay on top of the Wan2.1 base model. Point at the trainer
|
||||
# output checkpoint dir (its live/EMA weights are auto-resolved below) or a plain `.pt`/`.safetensors` file.
|
||||
# Default: the official One-Forcing checkpoint, downloaded via
|
||||
# hf download JiaqiFeng/OneForcing checkpoints/one_forcing.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
# The GAN discriminator head is a training-only submodule of fake_score, so it never appears in
|
||||
# the generator checkpoint and inference needs no discriminator wiring.
|
||||
transformer_path = "models/Diffusion_Transformer/One-Forcing/checkpoints/one_forcing.pt"
|
||||
# Load the EMA generator (transformer_ema/) when True, otherwise the live generator weights.
|
||||
# Only applies to trainer-output checkpoint dirs; a plain `.pt`/`.safetensors` file is loaded as-is.
|
||||
use_ema = False
|
||||
vae_path = None
|
||||
lora_path = None
|
||||
|
||||
# Other params
|
||||
sample_size = [480, 832]
|
||||
video_length = 81
|
||||
fps = 16
|
||||
|
||||
# One-Forcing causal inference config
|
||||
# `num_frame_per_block`: 1 = frame-wise (One-Forcing default), 3 = chunk-wise.
|
||||
num_frame_per_block = 1
|
||||
# Local attention window size (-1 for global attention)
|
||||
local_attn_size = -1
|
||||
# Others
|
||||
independent_first_frame = False
|
||||
context_noise = 0.0
|
||||
|
||||
# Use torch.float16 if GPU does not support torch.bfloat16
|
||||
# Some graphics cards, such as v100, 2080ti, do not support torch.bfloat16
|
||||
weight_dtype = torch.bfloat16
|
||||
prompt = "A stylish woman walks down a Tokyo street filled with warm glowing neon and animated city signage. She wears a black leather jacket, a long red dress, and black boots, and carries a black purse. She wears sunglasses and red lipstick. She walks confidently and casually. The street is damp and reflective, creating a mirror effect of the colorful lights. Many pedestrians walk about."
|
||||
negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
|
||||
# -------- One-Forcing step selector --------
|
||||
# One-Forcing bakes CFG into the student weights, so inference MUST use guidance_scale=1.0.
|
||||
# The official recipe generates each frame in a single step (denoising_step_list=[1000]):
|
||||
# 1-step (One-Forcing default): num_inference_steps = 1
|
||||
# 4-step (more stable, slower): num_inference_steps = 4 -> [1000, 750, 500, 250]
|
||||
# With stochastic_sampling=True, num_inference_steps=1 yields the warped single timestep [1000],
|
||||
# matching the official framewise 1-step generation.
|
||||
guidance_scale = 1.0
|
||||
num_inference_steps = 1
|
||||
seed = 43
|
||||
lora_weight = 0.55
|
||||
save_path = "samples/wan-videos-one-forcing"
|
||||
|
||||
device = set_multi_gpus_devices(ulysses_degree, ring_degree)
|
||||
config = OmegaConf.load(config_path)
|
||||
|
||||
# Load transformer with causal inference support if enabled
|
||||
transformer_additional_kwargs = OmegaConf.to_container(config['transformer_additional_kwargs'])
|
||||
transformer_additional_kwargs['local_attn_size'] = local_attn_size
|
||||
|
||||
transformer = WanTransformer3DModel_SelfForcing.from_pretrained(
|
||||
os.path.join(model_name, config['transformer_additional_kwargs'].get('transformer_subpath', 'transformer')),
|
||||
transformer_additional_kwargs=transformer_additional_kwargs,
|
||||
low_cpu_mem_usage=True,
|
||||
torch_dtype=weight_dtype,
|
||||
)
|
||||
|
||||
if transformer_path is not None:
|
||||
def _resolve_transformer_path(raw_path: str, prefer_ema: bool) -> str:
|
||||
"""Resolve a checkpoint path to the actual weights file to load.
|
||||
|
||||
File paths are returned unchanged so external `.pt` ckpts keep working. For trainer-output
|
||||
dirs, prefer the EMA generator when asked and available, otherwise fall back to the live
|
||||
generator. The FSDP trainer writes the EMA under `transformer_ema/`; `ema_transformer/` is
|
||||
kept as a compatibility alias.
|
||||
"""
|
||||
if os.path.isfile(raw_path):
|
||||
return raw_path
|
||||
if os.path.isdir(raw_path):
|
||||
candidates = []
|
||||
if prefer_ema:
|
||||
candidates.append(os.path.join(raw_path, "transformer_ema", "diffusion_pytorch_model.safetensors"))
|
||||
candidates.append(os.path.join(raw_path, "ema_transformer", "diffusion_pytorch_model.safetensors"))
|
||||
candidates.append(os.path.join(raw_path, "transformer", "diffusion_pytorch_model.safetensors"))
|
||||
candidates.append(os.path.join(raw_path, "diffusion_pytorch_model.safetensors"))
|
||||
for c in candidates:
|
||||
if os.path.isfile(c):
|
||||
return c
|
||||
raise FileNotFoundError(
|
||||
f"transformer_path={raw_path!r} is neither a file nor a checkpoint dir "
|
||||
f"with a known safetensors layout (transformer_ema/, transformer/ or root)."
|
||||
)
|
||||
|
||||
_raw_transformer_path = transformer_path
|
||||
transformer_path = _resolve_transformer_path(transformer_path, prefer_ema=use_ema)
|
||||
if transformer_path != _raw_transformer_path:
|
||||
print(f"use_ema={use_ema}: resolved {_raw_transformer_path} -> {transformer_path}")
|
||||
print(f"From checkpoint: {transformer_path}")
|
||||
if transformer_path.endswith("safetensors"):
|
||||
from safetensors.torch import load_file
|
||||
state_dict = load_file(transformer_path)
|
||||
else:
|
||||
state_dict = torch.load(transformer_path, map_location="cpu")
|
||||
|
||||
state_dict = state_dict["state_dict"] if "state_dict" in state_dict else state_dict
|
||||
state_dict = state_dict["generator_ema"] if "generator_ema" in state_dict else state_dict
|
||||
state_dict = state_dict["generator"] if "generator" in state_dict else state_dict
|
||||
# FSDP-saved ckpts keep the `model._fsdp_wrapped_module.` prefix; strip it before the generic
|
||||
# `model.` strip so both FSDP and plain ckpts land at bare parameter names.
|
||||
if any("._fsdp_wrapped_module." in k for k in state_dict.keys()):
|
||||
state_dict = {k.replace("model._fsdp_wrapped_module.", "model.", 1) if k.startswith("model._fsdp_wrapped_module.") else k: v for k, v in state_dict.items()}
|
||||
if any(k.startswith("model.") for k in state_dict.keys()):
|
||||
state_dict = {k.replace("model.", "", 1) if k.startswith("model.") else k: v for k, v in state_dict.items()}
|
||||
|
||||
m, u = transformer.load_state_dict(state_dict, strict=False)
|
||||
print(f"missing keys: {len(m)}, unexpected keys: {len(u)}")
|
||||
|
||||
# Get Vae
|
||||
vae = AutoencoderKLWan.from_pretrained(
|
||||
os.path.join(model_name, config['vae_kwargs'].get('vae_subpath', 'vae')),
|
||||
additional_kwargs=OmegaConf.to_container(config['vae_kwargs']),
|
||||
).to(weight_dtype)
|
||||
|
||||
if vae_path is not None:
|
||||
print(f"From checkpoint: {vae_path}")
|
||||
if vae_path.endswith("safetensors"):
|
||||
from safetensors.torch import load_file
|
||||
state_dict = load_file(vae_path)
|
||||
else:
|
||||
state_dict = torch.load(vae_path, map_location="cpu")
|
||||
state_dict = state_dict["state_dict"] if "state_dict" in state_dict else state_dict
|
||||
|
||||
m, u = vae.load_state_dict(state_dict, strict=False)
|
||||
print(f"missing keys: {len(m)}, unexpected keys: {len(u)}")
|
||||
|
||||
# Get Tokenizer
|
||||
tokenizer = AutoTokenizer.from_pretrained(
|
||||
os.path.join(model_name, config['text_encoder_kwargs'].get('tokenizer_subpath', 'tokenizer')),
|
||||
)
|
||||
|
||||
# Get Text encoder
|
||||
text_encoder = WanT5EncoderModel.from_pretrained(
|
||||
os.path.join(model_name, config['text_encoder_kwargs'].get('text_encoder_subpath', 'text_encoder')),
|
||||
additional_kwargs=OmegaConf.to_container(config['text_encoder_kwargs']),
|
||||
low_cpu_mem_usage=True,
|
||||
torch_dtype=weight_dtype,
|
||||
)
|
||||
|
||||
# Get Scheduler
|
||||
Chosen_Scheduler = {
|
||||
"Flow": FlowMatchEulerDiscreteScheduler,
|
||||
"Flow_Unipc": FlowUniPCMultistepScheduler,
|
||||
"Flow_DPM++": FlowDPMSolverMultistepScheduler,
|
||||
}[sampler_name]
|
||||
if sampler_name == "Flow_Unipc" or sampler_name == "Flow_DPM++":
|
||||
config['scheduler_kwargs']['shift'] = 1
|
||||
scheduler = Chosen_Scheduler(
|
||||
**filter_kwargs(Chosen_Scheduler, OmegaConf.to_container(config['scheduler_kwargs']))
|
||||
)
|
||||
|
||||
# Get Pipeline
|
||||
pipeline = WanSelfForcingPipeline(
|
||||
transformer=transformer,
|
||||
vae=vae,
|
||||
tokenizer=tokenizer,
|
||||
text_encoder=text_encoder,
|
||||
scheduler=scheduler,
|
||||
)
|
||||
|
||||
if ulysses_degree > 1 or ring_degree > 1:
|
||||
from functools import partial
|
||||
transformer.enable_multi_gpus_inference()
|
||||
if fsdp_dit:
|
||||
shard_fn = partial(shard_model, device_id=device, param_dtype=weight_dtype)
|
||||
pipeline.transformer = shard_fn(pipeline.transformer)
|
||||
print("Add FSDP DIT")
|
||||
if fsdp_text_encoder:
|
||||
shard_fn = partial(shard_model, device_id=device, param_dtype=weight_dtype)
|
||||
pipeline.text_encoder = shard_fn(pipeline.text_encoder)
|
||||
print("Add FSDP TEXT ENCODER")
|
||||
|
||||
if compile_dit:
|
||||
for i in range(len(pipeline.transformer.blocks)):
|
||||
pipeline.transformer.blocks[i] = torch.compile(pipeline.transformer.blocks[i])
|
||||
print("Add Compile")
|
||||
|
||||
# Quantize (when the mode carries an "_and_<quant>" suffix) and then place the pipeline.
|
||||
# The order lives inside the helper: quantization has to happen before the offload hooks
|
||||
# are installed.
|
||||
apply_gpu_memory_mode(pipeline, GPU_memory_mode, device, weight_dtype)
|
||||
|
||||
generator = torch.Generator(device=device).manual_seed(seed)
|
||||
|
||||
if lora_path is not None:
|
||||
pipeline = merge_lora(pipeline, lora_path, lora_weight, device=device, dtype=weight_dtype)
|
||||
|
||||
with torch.no_grad():
|
||||
video_length = int((video_length - 1) // vae.config.temporal_compression_ratio * vae.config.temporal_compression_ratio) + 1 if video_length != 1 else 1
|
||||
latent_frames = (video_length - 1) // vae.config.temporal_compression_ratio + 1
|
||||
|
||||
sample = pipeline(
|
||||
prompt,
|
||||
num_frames = video_length,
|
||||
negative_prompt = negative_prompt,
|
||||
height = sample_size[0],
|
||||
width = sample_size[1],
|
||||
generator = generator,
|
||||
guidance_scale = guidance_scale,
|
||||
num_inference_steps = num_inference_steps,
|
||||
shift = shift,
|
||||
num_frame_per_block = num_frame_per_block,
|
||||
independent_first_frame = independent_first_frame,
|
||||
context_noise = context_noise,
|
||||
stochastic_sampling = stochastic_sampling,
|
||||
).videos
|
||||
|
||||
if lora_path is not None:
|
||||
pipeline = unmerge_lora(pipeline, lora_path, lora_weight, device=device, dtype=weight_dtype)
|
||||
|
||||
def save_results():
|
||||
if not os.path.exists(save_path):
|
||||
os.makedirs(save_path, exist_ok=True)
|
||||
|
||||
index = len([path for path in os.listdir(save_path)]) + 1
|
||||
prefix = str(index).zfill(8)
|
||||
if video_length == 1:
|
||||
video_path = os.path.join(save_path, prefix + ".png")
|
||||
|
||||
image = sample[0, :, 0]
|
||||
image = image.transpose(0, 1).transpose(1, 2)
|
||||
image = (image * 255).numpy().astype(np.uint8)
|
||||
image = Image.fromarray(image)
|
||||
image.save(video_path)
|
||||
print(f"Saved image to: {video_path}")
|
||||
else:
|
||||
video_path = os.path.join(save_path, prefix + ".mp4")
|
||||
save_videos_grid(sample, video_path, fps=fps)
|
||||
|
||||
if ulysses_degree * ring_degree > 1:
|
||||
import torch.distributed as dist
|
||||
if dist.get_rank() == 0:
|
||||
save_results()
|
||||
else:
|
||||
save_results()
|
||||
@@ -173,7 +173,7 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=200 \
|
||||
--learning_rate=2.0e-06 \
|
||||
--learning_rate_critic=2.0e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
@@ -192,8 +192,10 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=3 \
|
||||
--use_kv_cache_training \
|
||||
--denoising_step_indices_list 1000 667 334 1 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--use_ema \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--randomize_step_indices \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
@@ -222,7 +224,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
| `--num_train_epochs` | Number of training epochs | 100 |
|
||||
| `--checkpointing_steps` | Save checkpoint every N steps | 200 |
|
||||
| `--learning_rate` | Generator learning rate | 2e-06 |
|
||||
| `--learning_rate_critic` | Critic learning rate | 2e-06 |
|
||||
| `--learning_rate_critic` | Critic learning rate | 4.0e-07 |
|
||||
| `--lr_scheduler` | LR scheduler type | `constant_with_warmup` |
|
||||
| `--lr_warmup_steps` | LR warmup steps | 100 |
|
||||
| `--output_dir` | Output directory | `output_dir_wan2.1_causal_forcing_dmd` |
|
||||
@@ -235,6 +237,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
| `--max_grad_norm` | Gradient clipping threshold | 10.0 |
|
||||
| `--trainable_modules` | Trainable modules (`"."` = all) | `"."` |
|
||||
| `--low_vram` | Enable low VRAM mode (offload VAE/text encoder) | - |
|
||||
| `--use_ema` | Maintain an EMA copy of the generator weights | - |
|
||||
|
||||
**Video Sampling Parameters**:
|
||||
|
||||
@@ -255,8 +258,8 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
|
||||
| Parameter | Description | Example Value |
|
||||
|-----------|-------------|---------------|
|
||||
| `--denoising_step_indices_list` | Denoising step indices (DMD core param). Example runs 4-step; parser default `[1000, 500]` = 2-step | `1000 667 334 1` |
|
||||
| `--real_guidance_scale` | CFG scale for the real-score (14B teacher) | 6.0 |
|
||||
| `--denoising_step_indices_list` | Denoising step indices (DMD core param). Example runs 4-step; parser default `[1000, 500]` = 2-step | `1000 750 500 250` |
|
||||
| `--real_guidance_scale` | CFG scale for the real-score (14B teacher) | 4.0 |
|
||||
| `--randomize_step_indices` | Randomize the denoising step indices during training | - |
|
||||
| `--fake_guidance_scale` | CFG scale for the fake-score (generator). 0.0 = no CFG | 0.0 |
|
||||
| `--gen_update_interval` | Generator update interval (generator updates every N critic steps) | 5 |
|
||||
@@ -273,7 +276,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
|
||||
### 5.4 Optional One-Forcing GAN and Precision Controls
|
||||
|
||||
These options reuse the [One-Forcing](https://github.com/Aurora-edu/One-Forcing) mechanisms in `scripts/wan2.1_self_forcing/train_distill.py`. All GAN/R1/R2 weights default to **0**, and both precision flags default to **off** in argparse. The AR diffusion, CCD, and DMD launchers enable `--fsdp_reduce_fp32`; GAN/R1/R2 and `--score_timestep_clamp` remain off. AR diffusion and CCD do not gain GAN losses.
|
||||
These options reuse the [One-Forcing](https://github.com/Aurora-edu/One-Forcing) mechanisms in `scripts/wan2.1_self_forcing/train_distill.py`. All GAN/R1/R2 weights default to **0**, and both precision flags default to **off** in argparse. The AR diffusion, CCD, and DMD launchers enable `--fsdp_reduce_fp32`; GAN/R1/R2 remain off, and the DMD launcher additionally enables `--score_timestep_clamp`. AR diffusion and CCD do not gain GAN losses.
|
||||
|
||||
The discriminator shares the **fake-score backbone**, with one learnable register token per selected feature layer. Its head is optimized by the critic optimizer, not the generator optimizer. Generator loss is `DMD + weighted GAN`; critic loss is `denoising + weighted GAN + R1 + R2`.
|
||||
|
||||
@@ -290,7 +293,7 @@ The discriminator shares the **fake-score backbone**, with one learnable registe
|
||||
| `--r2_weight` | Normalized finite-difference penalty on noisy generated latents | `0.0` |
|
||||
| `--r2_sigma` | R2 perturbation standard deviation | `0.01` |
|
||||
| `--fsdp_reduce_fp32` | FSDP FP32 gradient reduction; parameter gathering stays low precision | off (enabled in the launcher) |
|
||||
| `--score_timestep_clamp` | Clamp DMD, generator GAN, and critic timesteps to `[0.02, 0.98] * num_train_timesteps` | off |
|
||||
| `--score_timestep_clamp` | Clamp DMD, generator GAN, and critic timesteps to `[0.02, 0.98] * num_train_timesteps` | off (enabled in the DMD launcher) |
|
||||
|
||||
For logits `D_fake` and `D_real`, ordinary GAN losses are `softplus(-D_fake)` for the generator and `softplus(-D_real) + softplus(D_fake)` for the discriminator. Relativistic losses instead use `softplus(-(D_fake - D_real))` and `softplus(-(D_real - D_fake))`.
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=200 \
|
||||
--learning_rate=2.0e-06 \
|
||||
--learning_rate_critic=2.0e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
@@ -192,8 +192,10 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=3 \
|
||||
--use_kv_cache_training \
|
||||
--denoising_step_indices_list 1000 667 334 1 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--use_ema \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--randomize_step_indices \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
@@ -222,7 +224,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
| `--num_train_epochs` | 训练 epoch 数 | 100 |
|
||||
| `--checkpointing_steps` | 每 N 步保存 checkpoint | 200 |
|
||||
| `--learning_rate` | 生成器学习率 | 2e-06 |
|
||||
| `--learning_rate_critic` | 判别器学习率 | 2e-06 |
|
||||
| `--learning_rate_critic` | 判别器学习率 | 4.0e-07 |
|
||||
| `--lr_scheduler` | 学习率调度器 | `constant_with_warmup` |
|
||||
| `--lr_warmup_steps` | 学习率预热步数 | 100 |
|
||||
| `--output_dir` | 输出目录 | `output_dir_wan2.1_causal_forcing_dmd` |
|
||||
@@ -235,6 +237,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
| `--max_grad_norm` | 梯度裁剪阈值 | 10.0 |
|
||||
| `--trainable_modules` | 可训练模块(`"."` = 全部) | `"."` |
|
||||
| `--low_vram` | 低显存模式(卸载 VAE/文本编码器) | - |
|
||||
| `--use_ema` | 维护生成器权重的 EMA 副本 | - |
|
||||
|
||||
**视频采样参数**:
|
||||
|
||||
@@ -255,8 +258,8 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
|
||||
| 参数 | 说明 | 示例值 |
|
||||
|------|------|--------|
|
||||
| `--denoising_step_indices_list` | 去噪步骤索引(DMD 核心参数)。示例为 4 步;解析器默认 `[1000, 500]` = 2 步 | `1000 667 334 1` |
|
||||
| `--real_guidance_scale` | real-score(14B teacher)的 CFG scale | 6.0 |
|
||||
| `--denoising_step_indices_list` | 去噪步骤索引(DMD 核心参数)。示例为 4 步;解析器默认 `[1000, 500]` = 2 步 | `1000 750 500 250` |
|
||||
| `--real_guidance_scale` | real-score(14B teacher)的 CFG scale | 4.0 |
|
||||
| `--randomize_step_indices` | 训练时随机化去噪步骤索引 | - |
|
||||
| `--fake_guidance_scale` | fake-score(生成器)的 CFG scale。0.0 = 无 CFG | 0.0 |
|
||||
| `--gen_update_interval` | 生成器更新间隔(每 N 步判别器更新后更新 1 次生成器) | 5 |
|
||||
@@ -272,7 +275,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
|
||||
### 5.4 可选 One-Forcing GAN 与精度控制
|
||||
|
||||
这些选项复用 `scripts/wan2.1_self_forcing/train_distill.py` 中的 [One-Forcing](https://github.com/Aurora-edu/One-Forcing) 机制。GAN/R1/R2 权重默认均为 **0**,两个精度开关在 argparse 中默认均**关闭**。AR diffusion、CCD 和 DMD 的 launcher 均启用 `--fsdp_reduce_fp32`;GAN/R1/R2 和 `--score_timestep_clamp` 仍关闭。AR diffusion 和 CCD 不添加 GAN 损失。
|
||||
这些选项复用 `scripts/wan2.1_self_forcing/train_distill.py` 中的 [One-Forcing](https://github.com/Aurora-edu/One-Forcing) 机制。GAN/R1/R2 权重默认均为 **0**,两个精度开关在 argparse 中默认均**关闭**。AR diffusion、CCD 和 DMD 的 launcher 均启用 `--fsdp_reduce_fp32`;GAN/R1/R2 仍关闭,DMD 的 launcher 额外启用 `--score_timestep_clamp`。AR diffusion 和 CCD 不添加 GAN 损失。
|
||||
|
||||
判别器共享 **fake-score backbone**,每个选定特征层对应一个可学习 register token。判别器头由 critic optimizer 更新,不加入 generator optimizer。生成器损失为 `DMD + 加权 GAN`,critic 损失为 `denoising + 加权 GAN + R1 + R2`。
|
||||
|
||||
@@ -289,7 +292,7 @@ bash scripts/wan2.1_causal_forcing/train_causal_dmd.sh
|
||||
| `--r2_weight` | 噪声生成 latent 上的归一化有限差分正则权重 | `0.0` |
|
||||
| `--r2_sigma` | R2 扰动标准差 | `0.01` |
|
||||
| `--fsdp_reduce_fp32` | FSDP 使用 FP32 梯度归约,参数仍以低精度聚合 | 关闭(launcher 已启用) |
|
||||
| `--score_timestep_clamp` | 将 DMD、生成器 GAN 和 critic 时间步限制到 `[0.02, 0.98] * num_train_timesteps` | 关闭 |
|
||||
| `--score_timestep_clamp` | 将 DMD、生成器 GAN 和 critic 时间步限制到 `[0.02, 0.98] * num_train_timesteps` | 关闭(DMD launcher 已启用) |
|
||||
|
||||
设 logits 为 `D_fake` 和 `D_real`,普通 GAN 的生成器损失为 `softplus(-D_fake)`,判别器损失为 `softplus(-D_real) + softplus(D_fake)`。相对论模式分别使用 `softplus(-(D_fake - D_real))` 和 `softplus(-(D_real - D_fake))`。
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--output_dir="output_dir_wan2.1_causal_forcing_ar_diffusion" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
|
||||
@@ -41,6 +41,7 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--output_dir="output_dir_wan2.1_causal_forcing_ccd" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
|
||||
@@ -38,13 +38,14 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=200 \
|
||||
--learning_rate=2.0e-06 \
|
||||
--learning_rate_critic=2.0e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_causal_forcing_dmd" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
@@ -56,8 +57,10 @@ accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=3 \
|
||||
--use_kv_cache_training \
|
||||
--denoising_step_indices_list 1000 667 334 1 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--use_ema \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--randomize_step_indices \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
# Wan2.1 One-Forcing: Stable One-Step Autoregressive Video Generation
|
||||
|
||||
This document provides the complete workflow for **One-Forcing** on Wan2.1-T2V-1.3B, a recipe ported from [Aurora-edu/One-Forcing](https://github.com/Aurora-edu/One-Forcing).
|
||||
|
||||
> **What is One-Forcing?**
|
||||
>
|
||||
> One-Forcing (*Towards Stable One-Step Autoregressive Video Generation*) is **Causal-Forcing Stage 3 — Distribution Matching Distillation (DMD)** *plus* a **shared noised-latent adversarial critic**. The warm-start generator is produced by the first two Causal-Forcing stages, and One-Forcing then polishes it into a **stable one-step, frame-wise autoregressive** video model:
|
||||
>
|
||||
> 1. **Stage 1 — AR Diffusion** (`scripts/wan2.1_causal_forcing/train_ar_diffusion.py`): Train the model with **teacher forcing** on clean video latents, learning to denoise chunk-by-chunk (or frame-by-frame) in a causal autoregressive manner. This produces a strong AR backbone. See [README_TRAIN_AR_DIFFUSION.md](../wan2.1_causal_forcing/README_TRAIN_AR_DIFFUSION.md).
|
||||
> 2. **Stage 2 — Causal Consistency Distillation (CCD)** (`scripts/wan2.1_causal_forcing/train_causal_consistency_distill.py`): Distill the multi-step AR model into a **one-step-per-block** consistency model using an EMA teacher with CFG guidance. Its output checkpoint is the **warm-start generator** for One-Forcing. See [README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md).
|
||||
> 3. **Stage 3 — One-Forcing** (`train_one_forcing.py`, this document), which adds two mechanisms on top of DMD:
|
||||
> - **DMD core**: the generator matches the real distribution through a 14B real-score teacher and a 1.3B fake-score critic (`train_causal_dmd.py`).
|
||||
> - **Adversarial critic**: a discriminator head rides on the **fake-score backbone** (one register token per selected feature layer). Both the DMD denoising loss and the adversarial loss operate on the **same noised generated latent**, and the critic uses **one shared noise/timestep** for fake and real samples.
|
||||
> - **One step**: `denoising_step_list = [1000]` — each frame is generated in a single denoising step (the "One" in One-Forcing).
|
||||
>
|
||||
> Instead of training Stage 1/2 yourself, you can also warm-start from the **official ODE-initialized checkpoint** released by the One-Forcing authors (see Section 3).
|
||||
>
|
||||
> **The trainer** `scripts/wan2.1_one_forcing/train_one_forcing.py` implements the One-Forcing generator/critic GAN losses, the relativistic option, and finite-difference R1/R2 penalties. For the underlying DMD stage, see [README_TRAIN_CAUSAL_DMD_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL.md).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [1. Prerequisites](#1-prerequisites)
|
||||
- [2. Environment Setup](#2-environment-setup)
|
||||
- [3. Download Pretrained Models](#3-download-pretrained-models)
|
||||
- [4. Prepare Training Data](#4-prepare-training-data)
|
||||
- [4.1 Quick Demo Dataset](#41-quick-demo-dataset)
|
||||
- [4.2 Dataset Structure](#42-dataset-structure)
|
||||
- [4.3 metadata.json Format](#43-metadatajson-format)
|
||||
- [5. Training](#5-training)
|
||||
- [5.1 Quick Start](#51-quick-start)
|
||||
- [5.2 Recipe Parameters](#52-recipe-parameters)
|
||||
- [5.3 GAN Prerequisites](#53-gan-prerequisites)
|
||||
- [5.4 Optional Regularizers (Relativistic / R1 / R2)](#54-optional-regularizers-relativistic--r1--r2)
|
||||
- [6. Inference](#6-inference)
|
||||
- [6.1 Quick Start](#61-quick-start)
|
||||
- [6.2 1-Step vs 4-Step Selector](#62-1-step-vs-4-step-selector)
|
||||
- [6.3 First-Frame Schedule](#63-first-frame-schedule)
|
||||
- [7. Checkpoint Round-Trip](#7-checkpoint-round-trip)
|
||||
- [8. Additional Resources](#8-additional-resources)
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
One-Forcing is the final polish on top of a distilled generator, so it needs:
|
||||
|
||||
1. A **warm-start generator checkpoint** — a distilled/ODE generator that the DMD + adversarial critic then polishes (e.g. a **Causal-Forcing Stage-2 CCD** checkpoint, or the official ODE-initialized checkpoint in Section 3).
|
||||
2. A **Wan2.1-T2V-14B** model as the DMD real-score teacher.
|
||||
3. **Captioned real videos** — the adversarial critic needs clean real latents, so a prompt-only JSON is **not** sufficient (unlike plain DMD).
|
||||
|
||||
---
|
||||
|
||||
## 2. Environment Setup
|
||||
|
||||
**Method 1: Using requirements.txt**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**Method 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 yunchang xfuser modelscope openpyxl
|
||||
pip uninstall opencv-python opencv-contrib-python opencv-python-headless -y
|
||||
pip install opencv-python-headless
|
||||
pip install deepspeed==0.17.0 numpy==1.26.4
|
||||
```
|
||||
|
||||
**Method 3: Using Docker**
|
||||
|
||||
```bash
|
||||
# pull image
|
||||
docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
|
||||
|
||||
# enter image
|
||||
docker run -it -p 7860:7860 --network host --gpus all --security-opt seccomp:unconfined --shm-size 200g mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Download Pretrained Models
|
||||
|
||||
One-Forcing requires **two** pretrained models:
|
||||
|
||||
- **Wan2.1-T2V-1.3B**: base model for the generator/critic.
|
||||
- **Wan2.1-T2V-14B**: the non-causal real-score teacher used by DMD to compute the real distribution score.
|
||||
|
||||
```bash
|
||||
# Create model directory
|
||||
mkdir -p models/Diffusion_Transformer
|
||||
|
||||
# Download Wan2.1 T2V 1.3B (student base model)
|
||||
modelscope download --model Wan-AI/Wan2.1-T2V-1.3B --local_dir models/Diffusion_Transformer/Wan2.1-T2V-1.3B
|
||||
|
||||
# Download Wan2.1 T2V 14B (DMD real-score teacher)
|
||||
modelscope download --model Wan-AI/Wan2.1-T2V-14B --local_dir models/Diffusion_Transformer/Wan2.1-T2V-14B
|
||||
```
|
||||
|
||||
The official One-Forcing checkpoints are hosted on HuggingFace (`JiaqiFeng/OneForcing`):
|
||||
|
||||
```bash
|
||||
# Warm-start generator for training (the official ODE-initialized checkpoint),
|
||||
# loaded via --ode_transformer_path.
|
||||
hf download JiaqiFeng/OneForcing checkpoints/framewise/causal_ode.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
|
||||
# Trained One-Forcing generator for inference (see Section 6).
|
||||
hf download JiaqiFeng/OneForcing checkpoints/one_forcing.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
```
|
||||
|
||||
If you trained Causal-Forcing Stage 1/2 yourself, you can instead point `--ode_transformer_path` at your own Stage-2 CCD checkpoint.
|
||||
|
||||
---
|
||||
|
||||
## 4. Prepare Training Data
|
||||
|
||||
One-Forcing enables the GAN branch by default (`--gan_g_weight=0.03 --gan_d_weight=0.03`), so the data loader always uses an **ImageVideoDataset**: the adversarial critic needs **captioned real videos** to build its clean latents. A prompt-only JSON is **not** sufficient (unlike plain DMD).
|
||||
|
||||
### 4.1 Quick Demo Dataset
|
||||
|
||||
```bash
|
||||
# Download official demo dataset
|
||||
modelscope download --dataset PAI/X-Fun-Videos-Demo --local_dir ./datasets/X-Fun-Videos-Demo
|
||||
```
|
||||
|
||||
### 4.2 Dataset Structure
|
||||
|
||||
Same as Causal-Forcing Stage 1/2. See [README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md#42-dataset-structure) for details.
|
||||
|
||||
### 4.3 metadata.json Format
|
||||
|
||||
Each entry needs `file_path`, `type="video"`, and `text`; the launcher uses the `metadata_add_width_height.json` variant, which additionally carries `width`/`height` so bucketing does not need to decode the media files:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "train/00000000.mp4",
|
||||
"text": "A young woman gently turns her head to the right, tilting it slightly as she offers a soft greeting...",
|
||||
"type": "video",
|
||||
"width": 832,
|
||||
"height": 480
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
> **Note**: Keep enough frames per video for `--video_sample_stride=1` and `--video_sample_n_frames=81`: the GAN branch aborts when the source yields fewer latent frames than `score_num_frames=21` (see Section 5.3).
|
||||
|
||||
---
|
||||
|
||||
## 5. Training
|
||||
|
||||
### 5.1 Quick Start
|
||||
|
||||
The ready-to-use launcher is [train_one_forcing.sh](./train_one_forcing.sh). It calls the One-Forcing trainer (`train_one_forcing.py`) with the GAN recipe enabled:
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export STAGE2_CKPT="models/Diffusion_Transformer/One-Forcing/checkpoints/framewise/causal_ode.pt"
|
||||
|
||||
accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--fsdp_auto_wrap_policy TRANSFORMER_BASED_WRAP \
|
||||
--fsdp_transformer_layer_cls_to_wrap=CasualWanAttentionBlock \
|
||||
--fsdp_sharding_strategy "FULL_SHARD" --fsdp_state_dict_type=SHARDED_STATE_DICT \
|
||||
--fsdp_backward_prefetch "BACKWARD_PRE" --fsdp_cpu_ram_efficient_loading False \
|
||||
scripts/wan2.1_one_forcing/train_one_forcing.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_pretrained_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--ode_transformer_path=$STAGE2_CKPT \
|
||||
--image_sample_size=640 \
|
||||
--video_sample_size=640 \
|
||||
--token_sample_size=640 \
|
||||
--fix_sample_size 480 832 \
|
||||
--video_sample_stride=1 \
|
||||
--video_sample_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--num_train_epochs=100 \
|
||||
--max_train_steps=200 \
|
||||
--checkpointing_steps=100 \
|
||||
--learning_rate=1.0e-05 \
|
||||
--learning_rate_critic=1.0e-05 \
|
||||
--lr_scheduler="constant" \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_one_forcing_framewise_1step" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.01 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=1 \
|
||||
--use_kv_cache_training \
|
||||
--use_ema \
|
||||
--ema_decay=0.99 \
|
||||
--denoising_step_indices_list 1000 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--score_timestep_clamp \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
--gan_g_weight=0.03 \
|
||||
--gan_d_weight=0.03 \
|
||||
--gan_feature_layers 21 29 \
|
||||
--gan_block_ffn_dim=2048 \
|
||||
--gan_block_num_heads=12 \
|
||||
--resume_from_checkpoint="latest" \
|
||||
--trainable_modules "."
|
||||
```
|
||||
|
||||
Or simply:
|
||||
|
||||
```bash
|
||||
bash scripts/wan2.1_one_forcing/train_one_forcing.sh
|
||||
```
|
||||
|
||||
### 5.2 Recipe Parameters
|
||||
|
||||
The One-Forcing recipe sets these launcher flags:
|
||||
|
||||
| Parameter | Launcher flag | Value |
|
||||
|------------------------|---------------|-------|
|
||||
| `denoising_step_list: [1000]` | `--denoising_step_indices_list` | `1000` (one step) |
|
||||
| `num_frame_per_block: 1` | `--num_frame_per_block` | `1` (frame-wise) |
|
||||
| `num_training_frames: 21` / `score_num_frames: 21` | `--video_sample_n_frames=81` @ 480×832 | 21 latent frames |
|
||||
| `guidance_scale: 5.0` | `--real_guidance_scale` | `6.0` (see CFG note below) |
|
||||
| `lr: 1e-5` / `lr_critic: 1e-5` | `--learning_rate` / `--learning_rate_critic` | `1.0e-05` |
|
||||
| `beta1: 0.0`, `beta2: 0.999`, `weight_decay: 0.01` | `--adam_beta1`, `--adam_beta2`, `--adam_weight_decay` | `0.0`, `0.999`, `0.01` |
|
||||
| `ema_weight: 0.99` | `--use_ema`, `--ema_decay` | on, `0.99` |
|
||||
| `max_steps: 200` | `--max_train_steps` | `200` |
|
||||
| `dfake_gen_update_ratio: 5` | `--gen_update_interval` | `5` |
|
||||
| `gan_feature_layers: [21, 29]` | `--gan_feature_layers` | `21 29` |
|
||||
| `gan_block_ffn_dim: 2048` | `--gan_block_ffn_dim` | `2048` |
|
||||
| `gan_block_num_heads: 12` | `--gan_block_num_heads` | `12` |
|
||||
| `gan_g_weight: 0.03`, `gan_d_weight: 0.03` | `--gan_g_weight`, `--gan_d_weight` | `0.03`, `0.03` |
|
||||
| `relativistic_discriminator: false` | `--relativistic_discriminator` | omitted (off) |
|
||||
| `r1_weight: 0.0`, `r2_weight: 0.0` | `--r1_weight`, `--r2_weight` | omitted (off) |
|
||||
| `context_noise: 0` | `--context_noise` | `0` (default) |
|
||||
|
||||
> **CFG convention**: this repo computes guidance as `cond + scale * (cond - uncond)`, and this recipe uses `--real_guidance_scale=6.0`.
|
||||
|
||||
For the general training/video-sampling parameters shared with the DMD stage, see [README_TRAIN_CAUSAL_DMD_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL.md#52-key-parameters) Section 5.2.
|
||||
|
||||
### 5.3 GAN Prerequisites
|
||||
|
||||
The GAN branch is validated at startup in `train_one_forcing.py`. It requires:
|
||||
|
||||
- **Captioned real videos** (`type="video"` + `file_path` + `text`), not a prompt-only JSON. Enabling any positive GAN/R1/R2 weight switches the loader from `TextDataset` to `ImageVideoDataset`.
|
||||
- **`--gradient_accumulation_steps=1`** and **no DeepSpeed** — the trainer raises `ValueError` otherwise.
|
||||
- **`--video_sample_stride=1`**: with `stride=2`, an 81-frame source yields ~10 latent frames, which is below `score_num_frames=21` and aborts.
|
||||
- **Valid feature-layer indices** for the fake-score backbone. The defaults `21 29` (layers) and `12` (heads) fit Wan2.1-T2V-1.3B; the head count must divide the fake-score hidden dimension.
|
||||
|
||||
Trainable fake-score and head master weights stay **FP32**; the forward pass uses the configured autocast/FSDP precision, and logits/softplus/finite-difference reductions are evaluated in FP32. `--fsdp_reduce_fp32` changes gradient-communication precision only.
|
||||
|
||||
### 5.4 Optional Regularizers (Relativistic / R1 / R2)
|
||||
|
||||
These regularizers are **off** by default. To experiment, append to the launcher:
|
||||
|
||||
```bash
|
||||
# Relativistic discriminator: use paired real/fake logit differences
|
||||
--relativistic_discriminator
|
||||
|
||||
# Finite-difference gradient penalties (APT form, not double-backward)
|
||||
--r1_weight=1.0 --r1_sigma=0.1 # on noisy real latents: r1_weight * mean((D_real' - D_real)^2)
|
||||
--r2_weight=0.001 --r2_sigma=0.01 # on noisy gen latents: 0.5 * r2_weight * mean(((D_fake' - D_fake)/sigma)^2)
|
||||
```
|
||||
|
||||
For logits `D_fake` and `D_real`, ordinary GAN losses are `softplus(-D_fake)` (generator) and `softplus(-D_real) + softplus(D_fake)` (discriminator); relativistic losses use the logit differences instead. Real/fake samples share timestep and noise; perturbations use independent noise.
|
||||
|
||||
---
|
||||
|
||||
## 6. Inference
|
||||
|
||||
### 6.1 Quick Start
|
||||
|
||||
The inference entry is [examples/wan2.1_one_forcing/predict_t2v.py](../../examples/wan2.1_one_forcing/predict_t2v.py). It loads the **1.3B student** (the 14B model is only the DMD teacher at training time) and runs frame-wise one-step generation. By default it uses the **official One-Forcing checkpoint**:
|
||||
|
||||
```bash
|
||||
hf download JiaqiFeng/OneForcing checkpoints/one_forcing.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
```
|
||||
|
||||
```python
|
||||
# examples/wan2.1_one_forcing/predict_t2v.py
|
||||
model_name = "models/Diffusion_Transformer/Wan2.1-T2V-1.3B"
|
||||
transformer_path = "models/Diffusion_Transformer/One-Forcing/checkpoints/one_forcing.pt"
|
||||
use_ema = False # only applies to trainer-output checkpoint dirs
|
||||
stochastic_sampling = True # DMD-distilled student
|
||||
guidance_scale = 1.0 # CFG is baked into the distilled weights
|
||||
num_inference_steps = 1 # One-Forcing one-step generation
|
||||
num_frame_per_block = 1 # frame-wise
|
||||
sample_size = [480, 832]
|
||||
video_length = 81
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
python examples/wan2.1_one_forcing/predict_t2v.py
|
||||
```
|
||||
|
||||
`transformer_path` may be the official checkpoint file, a plain `.pt`/`.safetensors` file, or a trainer-output **checkpoint dir** (its live/EMA weights are auto-resolved). The GAN discriminator head is a training-only submodule of fake_score, so inference needs **no discriminator wiring**.
|
||||
|
||||
### 6.2 1-Step vs 4-Step Selector
|
||||
|
||||
With `stochastic_sampling=True`, `num_inference_steps` selects the warped timestep schedule:
|
||||
|
||||
| `num_inference_steps` | Denoise timesteps | Use case |
|
||||
|-----------------------|-------------------|----------|
|
||||
| `1` (One-Forcing default) | `[1000]` | Fastest; frame-wise one-step generation |
|
||||
| `4` | `[1000, 750, 500, 250]` | More stable, slower |
|
||||
|
||||
### 6.3 First-Frame Schedule
|
||||
|
||||
The shared `WanSelfForcingPipeline` applies **one denoising schedule to all blocks**. A separate first-frame schedule (e.g. `[1000, 750, 500, 250]` for the first frame and `[1000]` for later frames) is therefore not applied; the pipeline is shared by multiple examples and is intentionally left unmodified.
|
||||
|
||||
---
|
||||
|
||||
## 7. Checkpoint Round-Trip
|
||||
|
||||
The FSDP trainer writes each `checkpoint-{step}/` as follows, so training output feeds inference with no key surgery:
|
||||
|
||||
| Component | Saved to | Contains `gan_head`? |
|
||||
|-----------|----------|----------------------|
|
||||
| **Generator** (live) | `checkpoint-{step}/diffusion_pytorch_model.safetensors` (root, via FSDP hook) | No |
|
||||
| **Generator** (EMA) | `checkpoint-{step}/transformer_ema/` | No |
|
||||
| **Fake-score + discriminator head** | `checkpoint-{step}/fake_score/` | Yes (FP32 head tensors) |
|
||||
|
||||
- The **generator never contains `gan_head`** keys (the head lives on fake-score), so `predict_t2v.py` loads it cleanly with `strict=False`.
|
||||
- `_resolve_transformer_path` in the inference script resolves, in order: `transformer_ema/`, `ema_transformer/` (compat alias), `transformer/`, then the root safetensors.
|
||||
- **Resume** GAN training with the same GAN/head configuration: the head is recreated from CLI flags before loading and restored from `fake_score/`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Additional Resources
|
||||
|
||||
- **One-Forcing (source recipe)**: https://github.com/Aurora-edu/One-Forcing
|
||||
- **Causal-Forcing (DMD stage)**: https://github.com/thu-ml/Causal-Forcing
|
||||
- **Stage 1 (AR Diffusion) training guide**: [README_TRAIN_AR_DIFFUSION.md](../wan2.1_causal_forcing/README_TRAIN_AR_DIFFUSION.md)
|
||||
- **Stage 2 (CCD) training guide**: [README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL.md)
|
||||
- **Underlying DMD trainer guide**: [README_TRAIN_CAUSAL_DMD_DISTILL.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL.md)
|
||||
- **Official GitHub**: https://github.com/aigc-apps/VideoX-Fun
|
||||
@@ -0,0 +1,355 @@
|
||||
# Wan2.1 One-Forcing:稳定的单步自回归视频生成
|
||||
|
||||
本文档提供在 Wan2.1-T2V-1.3B 上进行 **One-Forcing** 的完整工作流,该配方移植自 [Aurora-edu/One-Forcing](https://github.com/Aurora-edu/One-Forcing)。
|
||||
|
||||
> **什么是 One-Forcing?**
|
||||
>
|
||||
> One-Forcing(*Towards Stable One-Step Autoregressive Video Generation*)= **Causal-Forcing Stage 3 — 分布匹配蒸馏 (DMD)** *加上*一个**共享噪声 latent 的对抗判别器(critic)**。热启动生成器由 Causal-Forcing 的前两个阶段产出,One-Forcing 再将其打磨为**稳定的单步、逐帧自回归**视频模型:
|
||||
>
|
||||
> 1. **Stage 1 — AR Diffusion**(`scripts/wan2.1_causal_forcing/train_ar_diffusion.py`):在干净视频 latent 上以 **teacher forcing** 训练模型,学习按块(或按帧)因果自回归地去噪,产出强 AR 基座。参见 [README_TRAIN_AR_DIFFUSION_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_AR_DIFFUSION_zh-CN.md)。
|
||||
> 2. **Stage 2 — 因果一致性蒸馏 (CCD)**(`scripts/wan2.1_causal_forcing/train_causal_consistency_distill.py`):使用 EMA teacher + CFG 将多步 AR 模型蒸馏为**每块一步**的一致性模型,其产出 checkpoint 即 One-Forcing 的**热启动生成器**。参见 [README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md)。
|
||||
> 3. **Stage 3 — One-Forcing**(`train_one_forcing.py`,即本文档),在 DMD 之上增加两个机制:
|
||||
> - **DMD 核心**:生成器通过 14B real-score teacher 与 1.3B fake-score critic 匹配真实分布(`train_causal_dmd.py`)。
|
||||
> - **对抗 critic**:判别器头挂在 **fake-score backbone** 上(每个选定特征层对应一个 register token)。DMD 去噪损失与对抗损失作用在**同一个噪声生成 latent** 上,critic 对真实/生成样本使用**同一份共享噪声与时间步**。
|
||||
> - **单步**:`denoising_step_list = [1000]` —— 每帧在单个去噪步内生成(即 One-Forcing 中的 “One”)。
|
||||
>
|
||||
> 你也可以不自己训练 Stage 1/2,而是直接使用 One-Forcing 作者发布的**官方 ODE 初始化 checkpoint** 作为热启动权重(见第三节)。
|
||||
>
|
||||
> **训练器** `scripts/wan2.1_one_forcing/train_one_forcing.py` 实现了 One-Forcing 的生成器/critic GAN 损失、相对论(relativistic)选项以及有限差分 R1/R2 正则。底层 DMD 阶段请参见 [README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md)。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
- [一、前置条件](#一前置条件)
|
||||
- [二、环境配置](#二环境配置)
|
||||
- [三、下载预训练模型](#三下载预训练模型)
|
||||
- [四、数据准备](#四数据准备)
|
||||
- [4.1 快速测试数据集](#41-快速测试数据集)
|
||||
- [4.2 数据集结构](#42-数据集结构)
|
||||
- [4.3 metadata.json 格式](#43-metadatajson-格式)
|
||||
- [五、训练](#五训练)
|
||||
- [5.1 快速开始](#51-快速开始)
|
||||
- [5.2 配方参数](#52-配方参数)
|
||||
- [5.3 GAN 前置要求](#53-gan-前置要求)
|
||||
- [5.4 可选正则(相对论 / R1 / R2)](#54-可选正则相对论--r1--r2)
|
||||
- [六、推理](#六推理)
|
||||
- [6.1 快速开始](#61-快速开始)
|
||||
- [6.2 单步与 4 步选择器](#62-单步与-4-步选择器)
|
||||
- [6.3 首帧调度](#63-首帧调度)
|
||||
- [七、Checkpoint 往返(训练→推理)](#七checkpoint-往返训练推理)
|
||||
- [八、更多资源](#八更多资源)
|
||||
|
||||
---
|
||||
|
||||
## 一、前置条件
|
||||
|
||||
One-Forcing 是在已蒸馏生成器之上的最后打磨,因此需要:
|
||||
|
||||
1. **热启动生成器 checkpoint** —— 一个已蒸馏/ODE 生成器,由 DMD + 对抗 critic 进一步打磨(例如 **Causal-Forcing Stage-2 CCD** checkpoint,或第三节的官方 ODE 初始化 checkpoint)。
|
||||
2. **Wan2.1-T2V-14B** 模型,作为 DMD real-score teacher。
|
||||
3. **带描述的真实视频** —— 对抗 critic 需要干净的真实 latent,因此仅有提示词的 JSON **不够用**(这一点与普通 DMD 不同)。
|
||||
|
||||
---
|
||||
|
||||
## 二、环境配置
|
||||
|
||||
**方式 1:使用 requirements.txt**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**方式 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 yunchang xfuser modelscope openpyxl
|
||||
pip uninstall opencv-python opencv-contrib-python opencv-python-headless -y
|
||||
pip install opencv-python-headless
|
||||
pip install deepspeed==0.17.0 numpy==1.26.4
|
||||
```
|
||||
|
||||
**方式 3:使用 Docker**
|
||||
|
||||
```bash
|
||||
# Pull the image
|
||||
docker pull mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
|
||||
|
||||
# Enter the container
|
||||
docker run -it -p 7860:7860 --network host --gpus all --security-opt seccomp:unconfined --shm-size 200g mybigpai-public-registry.cn-beijing.cr.aliyuncs.com/easycv/torch_cuda:cogvideox_fun
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、下载预训练模型
|
||||
|
||||
One-Forcing 需要**两个**预训练模型:
|
||||
|
||||
- **Wan2.1-T2V-1.3B**:生成器/判别器的基础模型。
|
||||
- **Wan2.1-T2V-14B**:非因果的 real-score teacher,用于计算真实分布得分。
|
||||
|
||||
```bash
|
||||
# Create the model directory
|
||||
mkdir -p models/Diffusion_Transformer
|
||||
|
||||
# Download the Wan2.1 T2V 1.3B student base model
|
||||
modelscope download --model Wan-AI/Wan2.1-T2V-1.3B --local_dir models/Diffusion_Transformer/Wan2.1-T2V-1.3B
|
||||
|
||||
# Download the Wan2.1 T2V 14B real-score teacher
|
||||
modelscope download --model Wan-AI/Wan2.1-T2V-14B --local_dir models/Diffusion_Transformer/Wan2.1-T2V-14B
|
||||
```
|
||||
|
||||
One-Forcing 官方 checkpoint 托管在 HuggingFace(`JiaqiFeng/OneForcing`):
|
||||
|
||||
```bash
|
||||
# 训练用热启动生成器(官方 ODE 初始化 checkpoint),通过 --ode_transformer_path 加载。
|
||||
hf download JiaqiFeng/OneForcing checkpoints/framewise/causal_ode.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
|
||||
# 推理用已训练 One-Forcing 生成器(见第六节)。
|
||||
hf download JiaqiFeng/OneForcing checkpoints/one_forcing.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
```
|
||||
|
||||
如果你自己训练了 Causal-Forcing Stage 1/2,也可以将 `--ode_transformer_path` 指向你产出的 Stage-2 CCD checkpoint。
|
||||
|
||||
---
|
||||
|
||||
## 四、数据准备
|
||||
|
||||
One-Forcing 默认开启 GAN 分支(`--gan_g_weight=0.03 --gan_d_weight=0.03`),因此数据加载始终使用 **ImageVideoDataset**:对抗 critic 需要**带描述的真实视频**来构建干净 latent,仅有提示词的 JSON **不够用**(这一点与普通 DMD 不同)。
|
||||
|
||||
### 4.1 快速测试数据集
|
||||
|
||||
```bash
|
||||
# Download the official demo dataset
|
||||
modelscope download --dataset PAI/X-Fun-Videos-Demo --local_dir ./datasets/X-Fun-Videos-Demo
|
||||
```
|
||||
|
||||
### 4.2 数据集结构
|
||||
|
||||
与 Causal-Forcing Stage 1/2 相同。详见 [README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md#42-数据集结构)。
|
||||
|
||||
### 4.3 metadata.json 格式
|
||||
|
||||
每条记录需要 `file_path`、`type="video"` 与 `text`;启动脚本使用 `metadata_add_width_height.json` 变体,额外携带 `width`/`height`,分桶训练无需现场解码媒体文件:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"file_path": "train/00000000.mp4",
|
||||
"text": "A young woman gently turns her head to the right, tilting it slightly as she offers a soft greeting...",
|
||||
"type": "video",
|
||||
"width": 832,
|
||||
"height": 480
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
> **说明**:每条视频须有足够的帧数以满足 `--video_sample_stride=1` 与 `--video_sample_n_frames=81`:当源视频产出的 latent 帧数少于 `score_num_frames=21` 时,GAN 分支会中止(见 5.3)。
|
||||
|
||||
---
|
||||
|
||||
## 五、训练
|
||||
|
||||
### 5.1 快速开始
|
||||
|
||||
可直接使用的启动脚本为 [train_one_forcing.sh](./train_one_forcing.sh)。它调用 One-Forcing 训练器(`train_one_forcing.py`)并开启 GAN 配方:
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export STAGE2_CKPT="models/Diffusion_Transformer/One-Forcing/checkpoints/framewise/causal_ode.pt"
|
||||
|
||||
accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--fsdp_auto_wrap_policy TRANSFORMER_BASED_WRAP \
|
||||
--fsdp_transformer_layer_cls_to_wrap=CasualWanAttentionBlock \
|
||||
--fsdp_sharding_strategy "FULL_SHARD" --fsdp_state_dict_type=SHARDED_STATE_DICT \
|
||||
--fsdp_backward_prefetch "BACKWARD_PRE" --fsdp_cpu_ram_efficient_loading False \
|
||||
scripts/wan2.1_one_forcing/train_one_forcing.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_pretrained_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--ode_transformer_path=$STAGE2_CKPT \
|
||||
--image_sample_size=640 \
|
||||
--video_sample_size=640 \
|
||||
--token_sample_size=640 \
|
||||
--fix_sample_size 480 832 \
|
||||
--video_sample_stride=1 \
|
||||
--video_sample_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--num_train_epochs=100 \
|
||||
--max_train_steps=200 \
|
||||
--checkpointing_steps=100 \
|
||||
--learning_rate=1.0e-05 \
|
||||
--learning_rate_critic=1.0e-05 \
|
||||
--lr_scheduler="constant" \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_one_forcing_framewise_1step" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.01 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=1 \
|
||||
--use_kv_cache_training \
|
||||
--use_ema \
|
||||
--ema_decay=0.99 \
|
||||
--denoising_step_indices_list 1000 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--score_timestep_clamp \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
--gan_g_weight=0.03 \
|
||||
--gan_d_weight=0.03 \
|
||||
--gan_feature_layers 21 29 \
|
||||
--gan_block_ffn_dim=2048 \
|
||||
--gan_block_num_heads=12 \
|
||||
--resume_from_checkpoint="latest" \
|
||||
--trainable_modules "."
|
||||
```
|
||||
|
||||
或直接运行:
|
||||
|
||||
```bash
|
||||
bash scripts/wan2.1_one_forcing/train_one_forcing.sh
|
||||
```
|
||||
|
||||
### 5.2 配方参数
|
||||
|
||||
One-Forcing 配方设置以下 launcher 参数:
|
||||
|
||||
| 参数 | Launcher 参数 | 取值 |
|
||||
|--------------------|---------------|------|
|
||||
| `denoising_step_list: [1000]` | `--denoising_step_indices_list` | `1000`(单步) |
|
||||
| `num_frame_per_block: 1` | `--num_frame_per_block` | `1`(逐帧) |
|
||||
| `num_training_frames: 21` / `score_num_frames: 21` | `--video_sample_n_frames=81` @ 480×832 | 21 个 latent 帧 |
|
||||
| `guidance_scale: 5.0` | `--real_guidance_scale` | `6.0`(见下方 CFG 说明) |
|
||||
| `lr: 1e-5` / `lr_critic: 1e-5` | `--learning_rate` / `--learning_rate_critic` | `1.0e-05` |
|
||||
| `beta1: 0.0`、`beta2: 0.999`、`weight_decay: 0.01` | `--adam_beta1`、`--adam_beta2`、`--adam_weight_decay` | `0.0`、`0.999`、`0.01` |
|
||||
| `ema_weight: 0.99` | `--use_ema`、`--ema_decay` | 开启、`0.99` |
|
||||
| `max_steps: 200` | `--max_train_steps` | `200` |
|
||||
| `dfake_gen_update_ratio: 5` | `--gen_update_interval` | `5` |
|
||||
| `gan_feature_layers: [21, 29]` | `--gan_feature_layers` | `21 29` |
|
||||
| `gan_block_ffn_dim: 2048` | `--gan_block_ffn_dim` | `2048` |
|
||||
| `gan_block_num_heads: 12` | `--gan_block_num_heads` | `12` |
|
||||
| `gan_g_weight: 0.03`、`gan_d_weight: 0.03` | `--gan_g_weight`、`--gan_d_weight` | `0.03`、`0.03` |
|
||||
| `relativistic_discriminator: false` | `--relativistic_discriminator` | 不传(关闭) |
|
||||
| `r1_weight: 0.0`、`r2_weight: 0.0` | `--r1_weight`、`--r2_weight` | 不传(关闭) |
|
||||
| `context_noise: 0` | `--context_noise` | `0`(默认) |
|
||||
|
||||
> **CFG 约定**:本仓库的引导计算为 `cond + scale * (cond - uncond)`,本配方取 `--real_guidance_scale=6.0`。
|
||||
|
||||
与 DMD 阶段共用的通用训练/视频采样参数,参见 [README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md#52-关键参数) 5.2 节。
|
||||
|
||||
### 5.3 GAN 前置要求
|
||||
|
||||
GAN 分支在 `train_one_forcing.py` 启动时会被校验,要求:
|
||||
|
||||
- **带描述的真实视频**(`type="video"` + `file_path` + `text`),不能只提供提示词 JSON。任一 GAN/R1/R2 权重为正时,数据加载会从 `TextDataset` 切换为 `ImageVideoDataset`。
|
||||
- **`--gradient_accumulation_steps=1`** 且**不使用 DeepSpeed** —— 否则训练器抛出 `ValueError`。
|
||||
- **`--video_sample_stride=1`**:当 `stride=2` 时,81 帧源仅得到约 10 个 latent 帧,低于 `score_num_frames=21` 而中止。
|
||||
- fake-score backbone 上**有效的特征层索引**。默认 `21 29`(层)与 `12`(头)适用于 Wan2.1-T2V-1.3B;头数须整除 fake-score 隐藏维度。
|
||||
|
||||
可训练的 fake-score 与判别器头 master weights 保持 **FP32**;前向按配置使用 autocast/FSDP 混合精度,logits/softplus/有限差分归约在 FP32 下计算。`--fsdp_reduce_fp32` 只改变梯度通信精度。
|
||||
|
||||
### 5.4 可选正则(相对论 / R1 / R2)
|
||||
|
||||
这些正则默认**关闭**。若要实验,向 launcher 追加:
|
||||
|
||||
```bash
|
||||
# Relativistic discriminator: use paired real/fake logit differences
|
||||
--relativistic_discriminator
|
||||
|
||||
# Finite-difference gradient penalties (APT form, not double-backward)
|
||||
--r1_weight=1.0 --r1_sigma=0.1 # on noisy real latents: r1_weight * mean((D_real' - D_real)^2)
|
||||
--r2_weight=0.001 --r2_sigma=0.01 # on noisy gen latents: 0.5 * r2_weight * mean(((D_fake' - D_fake)/sigma)^2)
|
||||
```
|
||||
|
||||
设 logits 为 `D_fake` 与 `D_real`,普通 GAN 损失为 `softplus(-D_fake)`(生成器)与 `softplus(-D_real) + softplus(D_fake)`(判别器);相对论模式改用 logit 差值。真实/生成样本共用时间步与噪声,扰动噪声独立抽样。
|
||||
|
||||
---
|
||||
|
||||
## 六、推理
|
||||
|
||||
### 6.1 快速开始
|
||||
|
||||
推理入口为 [examples/wan2.1_one_forcing/predict_t2v.py](../../examples/wan2.1_one_forcing/predict_t2v.py)。它加载 **1.3B 学生模型**(14B 仅在训练时作为 DMD teacher),运行逐帧单步生成。默认使用 **One-Forcing 官方 checkpoint**:
|
||||
|
||||
```bash
|
||||
hf download JiaqiFeng/OneForcing checkpoints/one_forcing.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
```
|
||||
|
||||
```python
|
||||
# examples/wan2.1_one_forcing/predict_t2v.py
|
||||
model_name = "models/Diffusion_Transformer/Wan2.1-T2V-1.3B"
|
||||
transformer_path = "models/Diffusion_Transformer/One-Forcing/checkpoints/one_forcing.pt"
|
||||
use_ema = False # only applies to trainer-output checkpoint dirs
|
||||
stochastic_sampling = True # DMD-distilled student
|
||||
guidance_scale = 1.0 # CFG is baked into the distilled weights
|
||||
num_inference_steps = 1 # One-Forcing one-step generation
|
||||
num_frame_per_block = 1 # frame-wise
|
||||
sample_size = [480, 832]
|
||||
video_length = 81
|
||||
```
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
python examples/wan2.1_one_forcing/predict_t2v.py
|
||||
```
|
||||
|
||||
`transformer_path` 既可以是官方 checkpoint 文件、纯 `.pt`/`.safetensors` 文件,也可以是训练输出的 **checkpoint 目录**(其 live/EMA 权重会被自动解析)。GAN 判别器头是 fake_score 的训练专用子模块,因此推理**无需任何判别器接线**。
|
||||
|
||||
### 6.2 单步与 4 步选择器
|
||||
|
||||
当 `stochastic_sampling=True` 时,`num_inference_steps` 决定 warped 时间步调度:
|
||||
|
||||
| `num_inference_steps` | 去噪时间步 | 适用场景 |
|
||||
|-----------------------|------------|----------|
|
||||
| `1`(One-Forcing 默认) | `[1000]` | 最快;逐帧单步生成 |
|
||||
| `4` | `[1000, 750, 500, 250]` | 更稳定、更慢 |
|
||||
|
||||
### 6.3 首帧调度
|
||||
|
||||
共享的 `WanSelfForcingPipeline` 对**所有块使用同一份去噪调度**,因此此处不单独为首帧设置调度(例如首帧 `[1000, 750, 500, 250]`、后续帧 `[1000]`);该 pipeline 被多个 example 共用,有意保持不变。
|
||||
|
||||
---
|
||||
|
||||
## 七、Checkpoint 往返(训练→推理)
|
||||
|
||||
FSDP 训练器将每个 `checkpoint-{step}/` 按如下布局写出,因此训练产物可直接喂给推理,无需改动 key:
|
||||
|
||||
| 组件 | 保存位置 | 是否含 `gan_head`? |
|
||||
|------|----------|---------------------|
|
||||
| **生成器**(live) | `checkpoint-{step}/diffusion_pytorch_model.safetensors`(根目录,经 FSDP hook) | 否 |
|
||||
| **生成器**(EMA) | `checkpoint-{step}/transformer_ema/` | 否 |
|
||||
| **Fake-score + 判别器头** | `checkpoint-{step}/fake_score/` | 是(FP32 判别器头张量) |
|
||||
|
||||
- **生成器从不包含 `gan_head`** 键(判别器头挂在 fake-score 上),因此 `predict_t2v.py` 可用 `strict=False` 干净加载。
|
||||
- 推理脚本中的 `_resolve_transformer_path` 依次解析:`transformer_ema/`、`ema_transformer/`(兼容别名)、`transformer/`,最后到根目录 safetensors。
|
||||
- **恢复** GAN 训练时须使用相同的 GAN/判别器头配置:加载前会根据 CLI 参数重建判别器头,并从 `fake_score/` 还原。
|
||||
|
||||
---
|
||||
|
||||
## 八、更多资源
|
||||
|
||||
- **One-Forcing(源配方)**:https://github.com/Aurora-edu/One-Forcing
|
||||
- **Causal-Forcing(DMD 阶段)**:https://github.com/thu-ml/Causal-Forcing
|
||||
- **Stage 1(AR Diffusion)训练指南**:[README_TRAIN_AR_DIFFUSION_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_AR_DIFFUSION_zh-CN.md)
|
||||
- **Stage 2(CCD)训练指南**:[README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_CONSISTENCY_DISTILL_zh-CN.md)
|
||||
- **底层 DMD 训练器指南**:[README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md](../wan2.1_causal_forcing/README_TRAIN_CAUSAL_DMD_DISTILL_zh-CN.md)
|
||||
- **官方 GitHub**:https://github.com/aigc-apps/VideoX-Fun
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
# One-Forcing needs REAL videos to build the adversarial critic's clean_latents, so the dataset
|
||||
# must be a video meta (type=video + file_path), NOT a prompt-only json. The official demo
|
||||
# dataset X-Fun-Videos-Demo ships captioned videos and is normalized by --enable_bucket
|
||||
# --random_hw_adapt --training_with_video_token_length.
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# Warm-start generator: a distilled/ODE checkpoint that the DMD + adversarial critic then polish.
|
||||
# Default: the official One-Forcing ODE-initialized checkpoint, downloaded via
|
||||
# hf download JiaqiFeng/OneForcing checkpoints/framewise/causal_ode.pt --local-dir models/Diffusion_Transformer/One-Forcing
|
||||
# You can also point it at your own Causal-Forcing Stage-2 CCD checkpoint.
|
||||
export STAGE2_CKPT="models/Diffusion_Transformer/One-Forcing/checkpoints/framewise/causal_ode.pt"
|
||||
# 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
|
||||
|
||||
# One-Forcing = Causal-Forcing Stage-3 DMD + a shared noised-latent adversarial critic on fake_score.
|
||||
# - 1-step frame-wise generation: --denoising_step_indices_list 1000, --num_frame_per_block=1;
|
||||
# 21 latent frames from --video_sample_n_frames=81 @ 480x832.
|
||||
# - GAN critic: gan_g_weight/gan_d_weight=0.03, feature layers [21, 29], ffn 2048, heads 12,
|
||||
# non-relativistic, R1/R2 off. The head rides on fake_score and is training-only.
|
||||
# - GAN requires real videos, --gradient_accumulation_steps=1, no DeepSpeed, and --video_sample_stride=1
|
||||
# (stride=2 on 81-frame sources yields ~10 latent frames < score_num_frames=21 and aborts).
|
||||
# - guidance: local CFG is cond + scale*(cond - uncond), so --real_guidance_scale=6.0.
|
||||
# - optimizer/EMA/steps: lr=lr_critic=1e-5, betas (0.0, 0.999), weight_decay=0.01,
|
||||
# --use_ema --ema_decay=0.99, gen_update_interval=5, max_steps=200.
|
||||
accelerate launch --mixed_precision="bf16" --use_fsdp \
|
||||
--fsdp_auto_wrap_policy TRANSFORMER_BASED_WRAP \
|
||||
--fsdp_transformer_layer_cls_to_wrap=CasualWanAttentionBlock \
|
||||
--fsdp_sharding_strategy "FULL_SHARD" --fsdp_state_dict_type=SHARDED_STATE_DICT \
|
||||
--fsdp_backward_prefetch "BACKWARD_PRE" --fsdp_cpu_ram_efficient_loading False \
|
||||
scripts/wan2.1_one_forcing/train_one_forcing.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_pretrained_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--ode_transformer_path=$STAGE2_CKPT \
|
||||
--image_sample_size=640 \
|
||||
--video_sample_size=640 \
|
||||
--token_sample_size=640 \
|
||||
--fix_sample_size 480 832 \
|
||||
--video_sample_stride=1 \
|
||||
--video_sample_n_frames=81 \
|
||||
--train_batch_size=1 \
|
||||
--gradient_accumulation_steps=1 \
|
||||
--dataloader_num_workers=8 \
|
||||
--num_train_epochs=100 \
|
||||
--max_train_steps=200 \
|
||||
--checkpointing_steps=100 \
|
||||
--learning_rate=1.0e-05 \
|
||||
--learning_rate_critic=1.0e-05 \
|
||||
--lr_scheduler="constant" \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_one_forcing_framewise_1step" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.01 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-08 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--num_frame_per_block=1 \
|
||||
--use_kv_cache_training \
|
||||
--use_ema \
|
||||
--ema_decay=0.99 \
|
||||
--denoising_step_indices_list 1000 \
|
||||
--real_guidance_scale=6.0 \
|
||||
--score_timestep_clamp \
|
||||
--fake_guidance_scale=0.0 \
|
||||
--gen_update_interval=5 \
|
||||
--gan_g_weight=0.03 \
|
||||
--gan_d_weight=0.03 \
|
||||
--gan_feature_layers 21 29 \
|
||||
--gan_block_ffn_dim=2048 \
|
||||
--gan_block_num_heads=12 \
|
||||
--resume_from_checkpoint="latest" \
|
||||
--trainable_modules "."
|
||||
@@ -179,6 +179,7 @@ The difference between DeepSpeed-Zero-2 and FSDP lies in whether to shard model
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -189,6 +190,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -204,21 +206,26 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -247,17 +254,19 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
| `--num_train_epochs` | Number of training epochs | 100 |
|
||||
| `--checkpointing_steps` | Save checkpoint every N steps | 50 |
|
||||
| `--learning_rate` | Initial learning rate (generator) | 2e-06 |
|
||||
| `--learning_rate_critic` | Initial learning rate (critic) | 2e-06 |
|
||||
| `--learning_rate_critic` | Initial learning rate (critic) | 4.0e-07 |
|
||||
| `--lr_scheduler` | Learning rate scheduler | `constant_with_warmup` |
|
||||
| `--lr_warmup_steps` | Learning rate warmup steps | 100 |
|
||||
| `--seed` | Random seed | 42 |
|
||||
| `--output_dir` | Output directory | `output_dir_wan2.1_self_forcing_distill` |
|
||||
| `--gradient_checkpointing` | Enable gradient checkpointing | - |
|
||||
| `--mixed_precision` | Mixed precision: `fp16/bf16` | `bf16` |
|
||||
| `--adam_weight_decay` | AdamW weight decay | 3e-2 |
|
||||
| `--adam_weight_decay` | AdamW weight decay | 0.0 |
|
||||
| `--adam_beta1` | AdamW beta1 | 0.0 |
|
||||
| `--adam_beta2` | AdamW beta2 | 0.999 |
|
||||
| `--adam_epsilon` | AdamW epsilon | 1e-10 |
|
||||
| `--vae_mini_batch` | VAE encoding mini-batch size | 1 |
|
||||
| `--max_grad_norm` | Gradient clipping threshold | 0.05 |
|
||||
| `--max_grad_norm` | Gradient clipping threshold | 10.0 |
|
||||
| `--fsdp_reduce_fp32` | Optional: reduce gradients in fp32 under FSDP mixed precision (default reduces in bf16) | - |
|
||||
| `--enable_bucket` | Enable bucket training, no cropping, group by resolution | - |
|
||||
| `--random_hw_adapt` | Auto-scale images/videos to random sizes in `[min_size, max_size]` range | - |
|
||||
@@ -277,7 +286,7 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
|-----|------|-------|
|
||||
| `--denoising_step_indices_list` | Denoising step indices list (core distillation parameter) | `1000 750 500 250` |
|
||||
| `--randomize_step_indices` | Randomize the denoising timestep indices during training | - |
|
||||
| `--real_guidance_scale` | Real guidance scale for scoring | 6.0 |
|
||||
| `--real_guidance_scale` | Real guidance scale for scoring | 4.0 |
|
||||
| `--score_timestep_clamp` | Optional: clamp DMD/critic timesteps to `[0.02, 0.98] * num_train_timesteps` | - |
|
||||
| `--fake_guidance_scale` | Fake guidance scale for scoring | 0.0 |
|
||||
| `--gen_update_interval` | Generator update interval | 5 |
|
||||
@@ -365,6 +374,7 @@ You can configure validation parameters to periodically generate test videos dur
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -375,6 +385,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" --use_fsdp --fsdp_auto_wrap_policy TRANSFORMER_BASED_WRAP --fsdp_transformer_layer_cls_to_wrap=CasualWanAttentionBlock --fsdp_sharding_strategy "FULL_SHARD" --fsdp_state_dict_type=SHARDED_STATE_DICT --fsdp_backward_prefetch "BACKWARD_PRE" --fsdp_cpu_ram_efficient_loading False scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -390,21 +401,26 @@ accelerate launch --mixed_precision="bf16" --use_fsdp --fsdp_auto_wrap_policy TR
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -427,6 +443,7 @@ python scripts/zero_to_bf16.py output_dir/checkpoint-{our-num-steps} output_dir/
|
||||
Training shell command is as follows:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -437,6 +454,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag true --use_deepspeed --deepspeed_config_file config/zero_stage3_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -452,21 +470,26 @@ accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -481,6 +504,7 @@ accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -491,6 +515,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -506,21 +531,26 @@ accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_dis
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -540,6 +570,7 @@ Assuming 2 machines, each with 8 GPUs:
|
||||
**Machine 0 (Master)**:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export MASTER_ADDR="192.168.1.100" # Master machine IP
|
||||
@@ -555,6 +586,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main_process_port=$MASTER_PORT --num_machines=$WORLD_SIZE --num_processes=$NUM_PROCESS --machine_rank=$RANK --use_deepspeed --deepspeed_config_file config/zero_stage2_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -570,21 +602,26 @@ accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -596,6 +633,7 @@ accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main
|
||||
**Machine 1 (Worker)**:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export MASTER_ADDR="192.168.1.100" # Same as Master
|
||||
|
||||
@@ -180,6 +180,7 @@ hf download gdhe17/Self-Forcing --local-dir models/Diffusion_Transformer/Self-Fo
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -190,6 +191,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -205,21 +207,26 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -248,17 +255,19 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
| `--num_train_epochs` | 训练 epoch 数 | 100 |
|
||||
| `--checkpointing_steps` | 每 N 步保存 checkpoint | 50 |
|
||||
| `--learning_rate` | 初始学习率(生成器) | 2e-06 |
|
||||
| `--learning_rate_critic` | 初始学习率(判别器) | 2e-06 |
|
||||
| `--learning_rate_critic` | 初始学习率(判别器) | 4.0e-07 |
|
||||
| `--lr_scheduler` | 学习率调度器 | `constant_with_warmup` |
|
||||
| `--lr_warmup_steps` | 学习率预热步数 | 100 |
|
||||
| `--seed` | 随机种子 | 42 |
|
||||
| `--output_dir` | 输出目录 | `output_dir_wan2.1_self_forcing_distill` |
|
||||
| `--gradient_checkpointing` | 激活重计算 | - |
|
||||
| `--mixed_precision` | 混合精度:`fp16/bf16` | `bf16` |
|
||||
| `--adam_weight_decay` | AdamW 权重衰减 | 3e-2 |
|
||||
| `--adam_weight_decay` | AdamW 权重衰减 | 0.0 |
|
||||
| `--adam_beta1` | AdamW beta1 | 0.0 |
|
||||
| `--adam_beta2` | AdamW beta2 | 0.999 |
|
||||
| `--adam_epsilon` | AdamW epsilon 值 | 1e-10 |
|
||||
| `--vae_mini_batch` | VAE 编码时的迷你批次大小 | 1 |
|
||||
| `--max_grad_norm` | 梯度裁剪阈值 | 0.05 |
|
||||
| `--max_grad_norm` | 梯度裁剪阈值 | 10.0 |
|
||||
| `--fsdp_reduce_fp32` | 可选:FSDP 混精下梯度以 fp32 做跨卡 reduce(默认 bf16) | - |
|
||||
| `--enable_bucket` | 启用分桶训练,不裁剪图片/视频,按分辨率分组训练 | - |
|
||||
| `--random_hw_adapt` | 自动缩放图片/视频到 `[min_size, max_size]` 范围内的随机尺寸 | - |
|
||||
@@ -278,7 +287,7 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
|-----|------|-------|
|
||||
| `--denoising_step_indices_list` | 去噪步骤列表(蒸馏核心参数) | `1000 750 500 250` |
|
||||
| `--randomize_step_indices` | 训练时随机化去噪 timestep 索引 | - |
|
||||
| `--real_guidance_scale` | 用于评分的真实 guidance scale | 6.0 |
|
||||
| `--real_guidance_scale` | 用于评分的真实 guidance scale | 4.0 |
|
||||
| `--score_timestep_clamp` | 可选:将 DMD/critic 的 timestep 截断到 `[0.02, 0.98] * num_train_timesteps` | - |
|
||||
| `--fake_guidance_scale` | 用于评分的虚拟 guidance scale | 0.0 |
|
||||
| `--gen_update_interval` | 生成器更新间隔 | 5 |
|
||||
@@ -366,6 +375,7 @@ accelerate launch --use_deepspeed --deepspeed_config_file config/zero_stage2_con
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -376,6 +386,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" --use_fsdp --fsdp_auto_wrap_policy TRANSFORMER_BASED_WRAP --fsdp_transformer_layer_cls_to_wrap=CasualWanAttentionBlock --fsdp_sharding_strategy "FULL_SHARD" --fsdp_state_dict_type=SHARDED_STATE_DICT --fsdp_backward_prefetch "BACKWARD_PRE" --fsdp_cpu_ram_efficient_loading False scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -391,21 +402,26 @@ accelerate launch --mixed_precision="bf16" --use_fsdp --fsdp_auto_wrap_policy TR
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -428,6 +444,7 @@ python scripts/zero_to_bf16.py output_dir/checkpoint-{our-num-steps} output_dir/
|
||||
训练 shell 命令如下:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -438,6 +455,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag true --use_deepspeed --deepspeed_config_file config/zero_stage3_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -453,21 +471,26 @@ accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -482,6 +505,7 @@ accelerate launch --zero_stage 3 --zero3_save_16bit_model true --zero3_init_flag
|
||||
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -492,6 +516,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -507,21 +532,26 @@ accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_dis
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -541,6 +571,7 @@ accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_dis
|
||||
**机器 0(Master)**:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export MASTER_ADDR="192.168.1.100" # Master 机器 IP
|
||||
@@ -556,6 +587,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main_process_port=$MASTER_PORT --num_machines=$WORLD_SIZE --num_processes=$NUM_PROCESS --machine_rank=$RANK --use_deepspeed --deepspeed_config_file config/zero_stage2_config.json --deepspeed_multinode_launcher standard scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -571,21 +603,26 @@ accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
@@ -597,6 +634,7 @@ accelerate launch --mixed_precision="bf16" --main_process_ip=$MASTER_ADDR --main
|
||||
**机器 1(Worker)**:
|
||||
```bash
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/X-Fun-Videos-Demo/"
|
||||
export DATASET_META_NAME="datasets/X-Fun-Videos-Demo/metadata_add_width_height.json"
|
||||
export MASTER_ADDR="192.168.1.100" # 与 Master 相同
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-1.3B/"
|
||||
export REAL_SCORE_MODEL_NAME="models/Diffusion_Transformer/Wan2.1-T2V-14B"
|
||||
export DATASET_NAME="datasets/internal_datasets/"
|
||||
export DATASET_META_NAME="datasets/internal_datasets/metadata.json"
|
||||
# NCCL_IB_DISABLE=1 and NCCL_P2P_DISABLE=1 are used in multi nodes without RDMA.
|
||||
@@ -9,6 +10,7 @@ NCCL_DEBUG=INFO
|
||||
accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_distill.py \
|
||||
--config_path="config/wan2.1/wan_civitai.yaml" \
|
||||
--pretrained_model_name_or_path=$MODEL_NAME \
|
||||
--real_score_model_name_or_path=$REAL_SCORE_MODEL_NAME \
|
||||
--train_data_dir=$DATASET_NAME \
|
||||
--train_data_meta=$DATASET_META_NAME \
|
||||
--image_sample_size=640 \
|
||||
@@ -24,21 +26,26 @@ accelerate launch --mixed_precision="bf16" scripts/wan2.1_self_forcing/train_dis
|
||||
--num_train_epochs=100 \
|
||||
--checkpointing_steps=50 \
|
||||
--learning_rate=2e-06 \
|
||||
--learning_rate_critic=2e-06 \
|
||||
--learning_rate_critic=4.0e-07 \
|
||||
--lr_scheduler="constant_with_warmup" \
|
||||
--lr_warmup_steps=100 \
|
||||
--seed=42 \
|
||||
--output_dir="output_dir_wan2.1_self_forcing_distill" \
|
||||
--gradient_checkpointing \
|
||||
--mixed_precision="bf16" \
|
||||
--adam_weight_decay=3e-2 \
|
||||
--fsdp_reduce_fp32 \
|
||||
--adam_weight_decay=0.0 \
|
||||
--adam_beta1=0.0 \
|
||||
--adam_beta2=0.999 \
|
||||
--adam_epsilon=1e-10 \
|
||||
--vae_mini_batch=1 \
|
||||
--max_grad_norm=0.05 \
|
||||
--max_grad_norm=10.0 \
|
||||
--random_hw_adapt \
|
||||
--training_with_video_token_length \
|
||||
--enable_bucket \
|
||||
--uniform_sampling \
|
||||
--denoising_step_indices_list 1000 750 500 250 \
|
||||
--real_guidance_scale=4.0 \
|
||||
--score_timestep_clamp \
|
||||
--use_kv_cache_training \
|
||||
--num_frame_per_block=3 \
|
||||
--train_mode="normal" \
|
||||
|
||||
@@ -5,6 +5,7 @@ import warnings
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
try:
|
||||
import flash_attn_interface
|
||||
|
||||
@@ -26,6 +26,29 @@ DETERMINISTIC_LOGVAR = -30.0
|
||||
TWorkItem = namedtuple("TWorkItem", ("input_tensor", "block_index"))
|
||||
|
||||
|
||||
def _load_raw_state_dict(path):
|
||||
"""Load a flat tensor state_dict from a .safetensors/.pth/.bin checkpoint.
|
||||
|
||||
Mirrors the normalization done in AutoencoderTinyWan.from_pretrained so that a
|
||||
companion encoder checkpoint can be borrowed the same way.
|
||||
"""
|
||||
path = os.fspath(path)
|
||||
if path.endswith(".safetensors"):
|
||||
from safetensors.torch import load_file
|
||||
|
||||
sd = load_file(path)
|
||||
else:
|
||||
sd = torch.load(path, map_location="cpu", weights_only=True)
|
||||
if isinstance(sd, dict):
|
||||
if isinstance(sd.get("state_dict"), dict):
|
||||
sd = sd["state_dict"]
|
||||
elif isinstance(sd.get("decoder"), dict):
|
||||
sd = sd["decoder"]
|
||||
if isinstance(sd, dict) and all(k.startswith("model.") for k in sd):
|
||||
sd = {k[len("model.") :]: v for k, v in sd.items()}
|
||||
return sd
|
||||
|
||||
|
||||
def conv(n_in, n_out, **kwargs):
|
||||
return nn.Conv2d(n_in, n_out, 3, padding=1, **kwargs)
|
||||
|
||||
@@ -566,6 +589,10 @@ class AutoencoderTinyWan(ModelMixin, ConfigMixin, FromOriginalModelMixin):
|
||||
if spatial_compression_ratio is None:
|
||||
spatial_compression_ratio = 8 * patch_size
|
||||
self.spatial_compression_ratio = spatial_compression_ratio
|
||||
# Persist the derived value into the frozen config as well: some pipelines
|
||||
# read `vae.config.spatial_compression_ratio` (not the attribute), and the
|
||||
# register_to_config decorator would otherwise keep the `None` default.
|
||||
self.register_to_config(spatial_compression_ratio=spatial_compression_ratio)
|
||||
|
||||
self.model = TAEWan(
|
||||
encoder_time_downscale=tuple(encoder_time_downscale),
|
||||
@@ -696,6 +723,33 @@ class AutoencoderTinyWan(ModelMixin, ConfigMixin, FromOriginalModelMixin):
|
||||
if all(k.startswith("model.") for k in state_dict):
|
||||
state_dict = {k[len("model.") :]: v for k, v in state_dict.items()}
|
||||
|
||||
# Borrow an encoder when the checkpoint is decoder-only. Some taew2_2_super
|
||||
# releases ship only the Super decoder; without an encoder, encode() would
|
||||
# raise. We merge encoder.* weights from a companion base checkpoint so the
|
||||
# model becomes a full encoder(base)+decoder(super) TAE.
|
||||
# Priority: explicit `encoder_path` (via additional_kwargs / vae_kwargs or
|
||||
# kwargs) > auto sibling found by stripping "_super" from the filename.
|
||||
encoder_path = None
|
||||
if isinstance(additional_kwargs, dict):
|
||||
encoder_path = additional_kwargs.get("encoder_path")
|
||||
if encoder_path is None:
|
||||
encoder_path = kwargs.get("encoder_path")
|
||||
if not any(k.startswith("encoder.") for k in state_dict):
|
||||
if encoder_path is None:
|
||||
root, ext = os.path.splitext(pretrained_model_path)
|
||||
sibling = root.replace("_super", "") + ext
|
||||
if sibling != pretrained_model_path and os.path.exists(sibling):
|
||||
encoder_path = sibling
|
||||
if encoder_path is not None and os.path.exists(encoder_path):
|
||||
enc_sd = {k: v for k, v in _load_raw_state_dict(encoder_path).items() if k.startswith("encoder.")}
|
||||
if enc_sd:
|
||||
state_dict = {**enc_sd, **state_dict}
|
||||
print(f"[TAE] decoder-only 权重:已从 {encoder_path} 借用 {len(enc_sd)} 个 encoder 键以支持 encode。")
|
||||
if not any(k.startswith("encoder.") for k in state_dict):
|
||||
print("[TAE][warn] 未找到 encoder 权重,将以 decoder_only 加载,encode() 不可用。"
|
||||
"可通过 vae_kwargs.encoder_path=<base taew2_2 权重路径> 指定,"
|
||||
"或把 base 权重放在与 super 同名(去掉 _super)的路径下。")
|
||||
|
||||
if "decoder.0.weight" in state_dict and "decoder.1.weight" not in state_dict:
|
||||
arch_variant, first_key, block_key = "super", "decoder.0.weight", "decoder.1.conv.0.weight"
|
||||
first_channels, first_kernel, block_shape = 512, (1, 1), (1024, 1, 7, 7)
|
||||
|
||||
@@ -23,15 +23,19 @@ from ..utils.fm_solvers_unipc import FlowUniPCMultistepScheduler
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def stochastic_sampling_timesteps(num_inference_steps, shift, device, num_timesteps=1000):
|
||||
def stochastic_sampling_timesteps(num_inference_steps, shift, device, num_timesteps=1000, timestep_list=None):
|
||||
"""Official FlashHead timestep schedule with shift transform.
|
||||
|
||||
Hardcoded schedules match the CF reference for distilled few-step inference:
|
||||
- 4-step: [1000, 750, 500, 250] — Stage 2 CCD (`causal_cd_framewise.yaml`)
|
||||
- 2-step: [1000, 500] — Stage 3 DMD (`causal_forcing_dmd_framewise_2step.yaml`)
|
||||
Anything else falls back to linspace.
|
||||
Anything else falls back to linspace. An explicit `timestep_list` overrides
|
||||
all of the above (per-block schedules such as the official One-Forcing
|
||||
first-frame list).
|
||||
"""
|
||||
if num_inference_steps == 4:
|
||||
if timestep_list is not None:
|
||||
timesteps = list(timestep_list)
|
||||
elif num_inference_steps == 4:
|
||||
timesteps = [1000, 750, 500, 250]
|
||||
elif num_inference_steps == 2:
|
||||
timesteps = [1000, 500]
|
||||
@@ -589,6 +593,7 @@ class WanSelfForcingPipeline(DiffusionPipeline):
|
||||
independent_first_frame: bool = True,
|
||||
context_noise: int = 0,
|
||||
stochastic_sampling: bool = True,
|
||||
first_frame_denoising_step_list: Optional[List[int]] = None,
|
||||
streaming: bool = False,
|
||||
decode_callback: Optional[Callable[[torch.Tensor, int], None]] = None,
|
||||
forcing_kv_enable: Optional[bool] = None,
|
||||
@@ -611,6 +616,12 @@ class WanSelfForcingPipeline(DiffusionPipeline):
|
||||
num_frame_per_block: Number of frames to generate per block.
|
||||
independent_first_frame: Whether to generate the first frame independently (T2V mode).
|
||||
context_noise: Context noise level for KV cache update (matches training config).
|
||||
first_frame_denoising_step_list: Optional explicit raw timestep list
|
||||
(e.g. [1000, 750, 500, 250]) used only for the block that starts at
|
||||
frame 0, mirroring the official One-Forcing
|
||||
`first_frame_denoising_step_list`: that block is the only one with
|
||||
an empty KV context and benefits from a multi-step schedule while
|
||||
all other blocks keep the global one. None disables the override.
|
||||
streaming: If True, decode each causal block into pixels right after it is
|
||||
generated (instead of accumulating all latents and decoding once at the end).
|
||||
The VAE's causal temporal cache is threaded across blocks so the result is
|
||||
@@ -696,6 +707,7 @@ class WanSelfForcingPipeline(DiffusionPipeline):
|
||||
in_prompt_embeds = prompt_embeds
|
||||
|
||||
# 4. Prepare timesteps
|
||||
custom_timesteps = timesteps
|
||||
if stochastic_sampling:
|
||||
timesteps = stochastic_sampling_timesteps(num_inference_steps, shift, device)
|
||||
elif isinstance(self.scheduler, FlowMatchEulerDiscreteScheduler):
|
||||
@@ -868,16 +880,40 @@ class WanSelfForcingPipeline(DiffusionPipeline):
|
||||
end_idx = start_idx + current_num_frames
|
||||
noisy_input = noise[:, :, start_idx:end_idx]
|
||||
|
||||
if not stochastic_sampling and isinstance(self.scheduler, FlowUniPCMultistepScheduler):
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device, shift=shift)
|
||||
timesteps = self.scheduler.timesteps
|
||||
elif not stochastic_sampling and isinstance(self.scheduler, FlowDPMSolverMultistepScheduler):
|
||||
self.scheduler.set_timesteps(device=device, sigmas=sampling_sigmas)
|
||||
timesteps = self.scheduler.timesteps
|
||||
else:
|
||||
# Official One-Forcing gives the block starting at frame 0 (the only
|
||||
# block with an empty KV context) its own multi-step schedule; all
|
||||
# other blocks keep the global one.
|
||||
if current_start_frame == 0 and first_frame_denoising_step_list is not None:
|
||||
if stochastic_sampling:
|
||||
block_timesteps = stochastic_sampling_timesteps(
|
||||
len(first_frame_denoising_step_list), shift, device,
|
||||
timestep_list=first_frame_denoising_step_list)
|
||||
elif isinstance(self.scheduler, FlowMatchEulerDiscreteScheduler):
|
||||
self.scheduler.set_timesteps(
|
||||
timesteps=list(first_frame_denoising_step_list), device=device)
|
||||
block_timesteps = self.scheduler.timesteps
|
||||
else:
|
||||
raise ValueError(
|
||||
"first_frame_denoising_step_list requires stochastic sampling or "
|
||||
f"FlowMatchEulerDiscreteScheduler, got {type(self.scheduler)}")
|
||||
self.scheduler._step_index = None
|
||||
|
||||
denoise_timesteps = timesteps[:-1] if stochastic_sampling else timesteps
|
||||
else:
|
||||
if not stochastic_sampling and isinstance(self.scheduler, FlowUniPCMultistepScheduler):
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device, shift=shift)
|
||||
timesteps = self.scheduler.timesteps
|
||||
elif not stochastic_sampling and isinstance(self.scheduler, FlowDPMSolverMultistepScheduler):
|
||||
self.scheduler.set_timesteps(device=device, sigmas=sampling_sigmas)
|
||||
timesteps = self.scheduler.timesteps
|
||||
elif (not stochastic_sampling and first_frame_denoising_step_list is not None
|
||||
and isinstance(self.scheduler, FlowMatchEulerDiscreteScheduler)):
|
||||
# Restore the global schedule overridden by the first-frame block
|
||||
timesteps, num_inference_steps = retrieve_timesteps(
|
||||
self.scheduler, num_inference_steps, device, custom_timesteps)
|
||||
else:
|
||||
self.scheduler._step_index = None
|
||||
block_timesteps = timesteps
|
||||
|
||||
denoise_timesteps = block_timesteps[:-1] if stochastic_sampling else block_timesteps
|
||||
with self.progress_bar(total=len(denoise_timesteps)) as progress_bar:
|
||||
for step_idx, t in enumerate(denoise_timesteps):
|
||||
# Per-frame timesteps for causal generation
|
||||
@@ -947,8 +983,8 @@ class WanSelfForcingPipeline(DiffusionPipeline):
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
if stochastic_sampling:
|
||||
t_i = (timesteps[step_idx] / 1000).to(weight_dtype)
|
||||
t_i_1 = (timesteps[step_idx + 1] / 1000).to(weight_dtype)
|
||||
t_i = (block_timesteps[step_idx] / 1000).to(weight_dtype)
|
||||
t_i_1 = (block_timesteps[step_idx + 1] / 1000).to(weight_dtype)
|
||||
denoised_pred = noisy_input - flow_pred * t_i
|
||||
noisy_input = (1 - t_i_1) * denoised_pred + t_i_1 * torch.randn(
|
||||
denoised_pred.shape, dtype=denoised_pred.dtype, device=device, generator=generator
|
||||
|
||||
@@ -91,7 +91,13 @@ def checkpoint_fsdp_forward(model, accumulated, *, context_fn=None, **kwargs):
|
||||
else:
|
||||
parameter.grad.add_(previous)
|
||||
if parameter.grad is not None:
|
||||
accumulated[parameter] = parameter.grad.detach().clone()
|
||||
if previous is None:
|
||||
accumulated[parameter] = parameter.grad.detach().clone()
|
||||
else:
|
||||
# Keep one independent accumulator per shard across blocks.
|
||||
# Replacing it with a clone retains both full copies until
|
||||
# previous_grads leaves this backward call.
|
||||
previous.copy_(parameter.grad)
|
||||
result = [None] * len(keys)
|
||||
for index, tensor in zip(ctx.tensor_indices, tensors):
|
||||
result[index] = tensor.grad
|
||||
|
||||
Reference in New Issue
Block a user