Compare commits

..
15 Commits
Author SHA1 Message Date
foreverpiano b435db8271 pd 2025-01-21 13:30:29 +00:00
foreverpiano 2e66366024 upd 2025-01-21 13:17:29 +00:00
foreverpiano 78bb902ab9 upd 2025-01-20 07:02:09 +00:00
foreverpiano 24a72543b8 upd 2025-01-20 06:49:06 +00:00
foreverpiano 1fa986b4a4 upd 2025-01-20 06:42:52 +00:00
foreverpiano 69714a58b0 upd 2025-01-20 06:41:20 +00:00
foreverpiano 86412d8ec6 upd 2025-01-20 06:37:19 +00:00
foreverpiano dc072bc989 upd 2025-01-19 15:11:09 +00:00
foreverpiano c530f8188b upd 2025-01-19 14:49:39 +00:00
foreverpiano 39350417e2 udp 2025-01-19 07:11:39 +00:00
foreverpiano 664cf4591d upd 2025-01-18 11:16:14 +00:00
foreverpiano 92475e19d8 upd 2025-01-18 07:53:12 +00:00
foreverpiano 5cc8daf0da upd 2025-01-17 17:05:17 +00:00
foreverpiano 99dbdaacac upd 2025-01-17 16:39:46 +00:00
foreverpiano dbe71057d5 update window 2025-01-17 16:20:44 +00:00
13 changed files with 1086 additions and 868 deletions
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
+16 -14
View File
@@ -120,49 +120,51 @@ Then you can run the finetune with:
```
bash scripts/finetune/finetune_mochi.sh # for mochi
```
**Note that for finetuning, we did not tune the hyperparameters in the provided script.**
**Note that for finetuning, we did not tune the hyperparameters in the provided script**
### ⚡ Lora Finetune
Hunyuan supports Lora fine-tuning of videos up to 720p. Demos and prompts of Black-Myth-Wukong can be found in [here](https://huggingface.co/FastVideo/Hunyuan-Black-Myth-Wukong-lora-weight). You can download the Lora weight through:
Demos and prompts of Black-Myth-Wukong can be found in [here](https://huggingface.co/FastVideo/Hunyuan-Black-Myth-Wukong-lora-weight). You can download the Lora weight through:
```bash
python scripts/huggingface/download_hf.py --repo_id=FastVideo/Hunyuan-Black-Myth-Wukong-lora-weight --local_dir=data/Hunyuan-Black-Myth-Wukong-lora-weight --repo_type=model
```
#### Minimum Hardware Requirement
- 40 GB GPU memory each for 2 GPUs with lora.
- 30 GB GPU memory each for 2 GPUs with CPU offload and lora.
Currently, both Mochi and Hunyuan models support Lora finetuning through diffusers. To generate personalized videos from your own dataset, you'll need to follow three main steps: dataset preparation, finetuning, and inference.
#### Dataset Preparation
We provide scripts to better help you get started to train on your own characters!
You can run this to organize your dataset to get the videos2caption.json before preprocess. Specify your video folder and corresponding caption folder (caption files should be .txt files and have the same name with its video):
You can run this to organize your dataset to get the videos2caption.json before preprocess. Specify your video folder and corresponding caption folder(Caption files should be .txt files and have the same name with its video):
```
python scripts/dataset_preparation/prepare_json_file.py --video_dir data/input_videos/ --prompt_dir data/captions/ --output_path data/output_folder/videos2caption.json --verbose
```
Also, we provide script to resize your videos:
```
python scripts/data_preprocess/resize_videos.py
python scripts/data_preprocess/resize_videos.py \
--input_dir data/raw_videos/ \
--output_dir data/resized_videos/ \
--width 1280 \
--height 720 \
--fps 30
```
#### Finetuning
After basic dataset preparation and preprocess, you can start to finetune your model using Lora:
```
bash scripts/finetune/finetune_hunyuan_hf_lora.sh
bash scripts/finetune/finetune_mochi_lora.sh
```
#### Inference
For inference with Lora checkpoint, you can run the following scripts with additional parameter `--lora_checkpoint_dir`:
For inference with Lora checkpoint, you can run the following scripts with Additional parameter --lora_checkpoint_dir:
```
bash scripts/inference/inference_hunyuan_hf.sh
bash scripts/inference/inference_mochi_hf.sh
```
**We also provide scripts for Mochi in the same directory.**
#### Minimum Hardware Requirement
- 40 GB GPU memory each for 2 GPUs with lora
- 30 GB GPU memory each for 2 GPUs with CPU offload and lora.
#### Finetune with Both Image and Video
Our codebase support finetuning with both image and video.
```bash
bash scripts/finetune/finetune_hunyuan.sh
bash scripts/finetune/finetune_mochi_lora_mix.sh
```
For Image-Video Mixture Fine-tuning, make sure to enable the `--group_frame` option in your script.
For Image-Video Mixture Fine-tuning, make sure to enable the --group_frame option in your script.
## 📑 Development Plan
+95 -2
View File
@@ -32,7 +32,7 @@ def attention(
return out
def parallel_attention(q, k, v, img_q_len, img_kv_len, text_mask):
def parallel_attention(q, k, v, img_q_len, img_kv_len, text_mask, sample_step, layer_id):
# 1GPU torch.Size([1, 11264, 24, 128]) tensor([ 0, 11275, 11520], device='cuda:0', dtype=torch.int32)
# 2GPU torch.Size([1, 5632, 24, 128]) tensor([ 0, 5643, 5888], device='cuda:0', dtype=torch.int32)
query, encoder_query = q
@@ -57,6 +57,90 @@ def parallel_attention(q, k, v, img_q_len, img_kv_len, text_mask):
sequence_length = query.size(1)
encoder_sequence_length = encoder_query.size(1)
# attn_map plot
num_heads = query.size(2)
map_q = query.transpose(1, 2) # [B, H, S, D]
map_k = torch.cat([key, encoder_key], dim=1).transpose(1, 2) # [B, H, S+T, D]
d_k = map_q.size(-1)
shape = (16, 45, 45)
q_coords = torch.tensor([get_block(i, shape=shape) for i in range(sequence_length)],
device='cuda', dtype=torch.int16)
k_coords = torch.tensor([get_block(i, shape=shape) for i in range(img_kv_len)],
device='cuda', dtype=torch.int16)
diffs = (q_coords.unsqueeze(1) - k_coords.unsqueeze(0)).abs() # [seq_len, kv_len, 3]
mask_t_diff = 6
mask_x_diff = 12
mask_y_diff = 12
mask_t = diffs[..., 0] <= mask_t_diff
mask_x = diffs[..., 1] <= mask_x_diff
mask_y = diffs[..., 2] <= mask_y_diff
valid_mask_2d = mask_t & mask_x & mask_y # [seq_len, kv_len]
del mask_t, mask_x, mask_y, diffs
# Pad mask for text part (all True)
full_valid_mask = F.pad(valid_mask_2d, (0, encoder_sequence_length), value=True) # [seq_len, kv_len+text_len]
img_mask_density = valid_mask_2d.float().mean().item()
full_mask_density = full_valid_mask.float().mean().item()
del valid_mask_2d
chunk_size = 512
save_attn = False #(sample_step == 1) and (layer_id == 59)
if save_attn:
attn_map_cumulated = torch.zeros((sequence_length, img_kv_len + encoder_sequence_length),
dtype=torch.float32, device='cuda')
# 逐head计算
for head_idx in range(num_heads):
current_q = map_q[:, head_idx:head_idx+1].to(dtype=torch.float32) # [B, 1, S, D]
current_k = map_k[:, head_idx:head_idx+1].to(dtype=torch.float32) # [B, 1, S+T, D]
valid_score_total = 0.0
all_score_total = 0.0
if save_attn:
head_attn_map = torch.zeros((sequence_length, img_kv_len + encoder_sequence_length),
dtype=torch.float32, device='cuda')
# 分块计算
for i in range(0, current_q.size(2), chunk_size):
chunk_end = min(i + chunk_size, current_q.size(2))
q_chunk = current_q[:, :, i:chunk_end] # [B, 1, chunk_size, D]
scores_32 = torch.matmul(
q_chunk,
current_k.transpose(-2, -1)
) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32, device='cuda'))
attn_weights = F.softmax(scores_32, dim=-1)
chunk_valid_mask = full_valid_mask[i:chunk_end].unsqueeze(0).unsqueeze(0)
valid_score_sum = (attn_weights * chunk_valid_mask).sum()
all_score_sum = attn_weights.sum()
valid_score_total += valid_score_sum.item()
all_score_total += all_score_sum.item()
# For last layer, accumulate attention weights
if save_attn:
head_attn_map[i:chunk_end] = attn_weights.squeeze(0).squeeze(0)
del scores_32, attn_weights
torch.cuda.empty_cache()
recall = valid_score_total / (all_score_total + 1e-9)
print(f"step{sample_step}_layer{layer_id}_head{head_idx}_window_diff_{mask_t_diff}_{mask_x_diff}_{mask_y_diff}_shape_{shape[0]}_{shape[1]}_{shape[2]}_img{img_mask_density:.4f}_full{full_mask_density:.4f}'s recall:", recall)
if save_attn:
attn_map_cumulated += head_attn_map
if save_attn:
attn_map_avg = attn_map_cumulated / num_heads
torch.save(attn_map_avg, f'logs/attn_map_step{sample_step}_layer{layer_id}.pt')
# Hint: please check encoder_query.shape
query = torch.cat([query, encoder_query], dim=1)
key = torch.cat([key, encoder_key], dim=1)
@@ -70,7 +154,7 @@ def parallel_attention(q, k, v, img_q_len, img_kv_len, text_mask):
causal=False,
dropout_p=0.0,
softmax_scale=None)
hidden_states, encoder_hidden_states = hidden_states.split_with_sizes(
(sequence_length, encoder_sequence_length), dim=1)
if get_sequence_parallel_state():
@@ -88,3 +172,12 @@ def parallel_attention(q, k, v, img_q_len, img_kv_len, text_mask):
attn = attn.reshape(b, s, -1)
return attn
def get_block(idx, shape=(16, 30, 30)):
t_size, x_size, y_size = shape
xy = x_size * y_size
t = idx // xy
r = idx % xy
x = r // y_size
y = r % y_size
return t, x, y
@@ -38,6 +38,7 @@ class MMDoubleStreamBlock(nn.Module):
qkv_bias: bool = False,
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
layer_id: int = 0,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
@@ -124,6 +125,8 @@ class MMDoubleStreamBlock(nn.Module):
**factory_kwargs,
)
self.hybrid_seq_parallel_attn = None
self.sample_step = 0
self.layer_id = layer_id
def enable_deterministic(self):
self.deterministic = True
@@ -207,6 +210,10 @@ class MMDoubleStreamBlock(nn.Module):
txt_q = self.txt_attn_q_norm(txt_q).to(txt_v)
txt_k = self.txt_attn_k_norm(txt_k).to(txt_v)
print("DOUBLE====")
print(img_q.shape, txt_q.shape)
self.sample_step += 1
attn = parallel_attention(
(img_q, txt_q),
(img_k, txt_k),
@@ -214,6 +221,8 @@ class MMDoubleStreamBlock(nn.Module):
img_q_len=img_q.shape[1],
img_kv_len=img_k.shape[1],
text_mask=text_mask,
sample_step=self.sample_step,
layer_id=self.layer_id,
)
# attention computation end
@@ -264,6 +273,7 @@ class MMSingleStreamBlock(nn.Module):
qk_scale: float = None,
dtype: Optional[torch.dtype] = None,
device: Optional[torch.device] = None,
layer_id: int = 0,
):
factory_kwargs = {"device": device, "dtype": dtype}
super().__init__()
@@ -304,6 +314,8 @@ class MMSingleStreamBlock(nn.Module):
**factory_kwargs,
)
self.hybrid_seq_parallel_attn = None
self.sample_step = 0
self.layer_id = layer_id
def enable_deterministic(self):
self.deterministic = True
@@ -354,6 +366,7 @@ class MMSingleStreamBlock(nn.Module):
), f"img_kk: {img_qq.shape}, img_q: {img_q.shape}, img_kk: {img_kk.shape}, img_k: {img_k.shape}"
img_q, img_k = img_qq, img_kk
self.sample_step += 1
attn = parallel_attention(
(img_q, txt_q),
(img_k, txt_k),
@@ -361,6 +374,8 @@ class MMSingleStreamBlock(nn.Module):
img_q_len=img_q.shape[1],
img_kv_len=img_k.shape[1],
text_mask=text_mask,
sample_step=self.sample_step,
layer_id=self.layer_id,
)
# attention computation end
@@ -521,6 +536,7 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
qk_norm=qk_norm,
qk_norm_type=qk_norm_type,
qkv_bias=qkv_bias,
layer_id=_,
**factory_kwargs,
) for _ in range(mm_double_blocks_depth)
])
@@ -534,6 +550,7 @@ class HYVideoDiffusionTransformer(ModelMixin, ConfigMixin):
mlp_act_type=mlp_act_type,
qk_norm=qk_norm,
qk_norm_type=qk_norm_type,
layer_id=_+mm_double_blocks_depth,
**factory_kwargs,
) for _ in range(mm_single_blocks_depth)
])
-102
View File
@@ -1,102 +0,0 @@
import triton
import triton.language as tl
CONFIG_LIST = [
triton.Config({"BLOCK_M": 256, "BLOCK_N": 32}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 128, "BLOCK_N": 64}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 128, "BLOCK_N": 32}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 64, "BLOCK_N": 128}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 64, "BLOCK_N": 64}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 64, "BLOCK_N": 32}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 32, "BLOCK_N": 64}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 32, "BLOCK_N": 128}, num_stages=2, num_warps=4),
triton.Config({"BLOCK_M": 32, "BLOCK_N": 256}, num_stages=2, num_warps=4),
]
@triton.autotune(
configs=CONFIG_LIST,
key=["M", "N"],
)
@triton.jit
def _modulate_fwd(
x_ptr, # *Pointer* to first input vector.
output_ptr, # *Pointer* to output vector.
scale_ptr,
shift_ptr,
m_stride,
s_stride,
M,
N,
seq_len,
BLOCK_M: tl.constexpr, # Number of elements each program should process.
BLOCK_N: tl.constexpr,
# NOTE: `constexpr` so it can be used as a shape value.
):
row_id = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
rows = row_id * BLOCK_M + tl.arange(0, BLOCK_M)
s_rows = (row_id // seq_len) * BLOCK_M
col_id = tl.program_id(axis=1)
cols = col_id * BLOCK_N + tl.arange(0, BLOCK_N)
x_ptrs = x_ptr + rows[:, None] * m_stride + cols[None, :]
scale_ptrs = scale_ptr + s_rows * s_stride + cols[None, :]
shift_ptrs = shift_ptr + s_rows * s_stride + cols[None, :]
col_mask = cols[None, :] < N
block_mask = (rows[:, None] < M) & col_mask
s_block_mask = col_mask
x = tl.load(x_ptrs, mask=block_mask, other=0.0)
scale = tl.load(scale_ptrs, mask=s_block_mask, other=0.0)
shift = tl.load(shift_ptrs, mask=s_block_mask, other=0.0)
output = x * (1 + scale) + shift
# Write x + y back to DRAM.
tl.store(output_ptr + rows[:, None] * m_stride + cols[None, :], output, mask=block_mask)
@triton.autotune(
configs=CONFIG_LIST,
key=["M", "N"],
)
@triton.jit
def _modulate_bwd(
dx_ptr, # *Pointer* to first input vector.
x_ptr,
dy_ptr, # *Pointer* to output vector.
scale_ptr,
dscale_ptr,
m_stride,
s_stride,
M,
N,
seq_len,
BLOCK_M: tl.constexpr, # Number of elements each program should process.
BLOCK_N: tl.constexpr,
# NOTE: `constexpr` so it can be used as a shape value.
):
row_id = tl.program_id(axis=0) # We use a 1D launch grid so axis is 0.
rows = row_id * BLOCK_M + tl.arange(0, BLOCK_M)
s_rows = (row_id // seq_len) * BLOCK_M
col_id = tl.program_id(axis=1)
cols = col_id * BLOCK_N + tl.arange(0, BLOCK_N)
x_ptrs = x_ptr + rows[:, None] * m_stride + cols[None, :]
dy_ptrs = dy_ptr + rows[:, None] * m_stride + cols[None, :]
dx_ptrs = dx_ptr + rows[:, None] * m_stride + cols[None, :]
dscale_ptrs = dscale_ptr + rows[:, None] * m_stride + cols[None, :]
scale_ptrs = scale_ptr + s_rows * s_stride + cols[None, :]
col_mask = cols[None, :] < N
block_mask = (rows[:, None] < M) & col_mask
s_block_mask = col_mask
x = tl.load(x_ptrs, mask=block_mask, other=0.0)
dy = tl.load(dy_ptrs, mask=block_mask, other=0.0)
scale = tl.load(scale_ptrs, mask=s_block_mask, other=0.0)
dx = dy * (1 + scale)
dscale = dy * x
# Write x + y back to DRAM.
tl.store(dx_ptrs, dx, mask=block_mask)
tl.store(dscale_ptrs, dscale, mask=block_mask)
-63
View File
@@ -1,63 +0,0 @@
import torch
import triton
from fastvideo.ops.modulate.k_modulate import _modulate_fwd, _modulate_bwd
class _FusedModulate(torch.autograd.Function):
@staticmethod
def forward(ctx, x, scale, shift):
y = torch.empty_like(x)
batch, seq_len, dim = x.shape
M = batch * seq_len
N = dim
x = x.view(-1, dim).contiguous()
scale = scale.view(-1, dim).contiguous()
shift = shift.view(-1, dim).contiguous()
def grid(meta):
return (
triton.cdiv(batch * seq_len, meta["BLOCK_M"]),
triton.cdiv(dim, meta["BLOCK_N"]),
)
_modulate_fwd[grid](x, y, scale, shift, x.stride(0), scale.stride(0), M, N, seq_len)
ctx.save_for_backward(x, scale)
ctx.batch = batch
ctx.seq_len = seq_len
ctx.dim = dim
return y
@staticmethod
def backward(ctx, dy): # pragma: no cover # this is covered, but called directly from C++
x, scale = ctx.saved_tensors
batch, seq_len, dim = ctx.batch, ctx.seq_len, ctx.dim
M = batch * seq_len
N = dim
# allocate output
dy = dy.contiguous()
dx = torch.empty_like(dy)
dscale = torch.empty_like(dy)
dshift = torch.sum(dy, dim=1)
def grid(meta):
return (
triton.cdiv(batch * seq_len, meta["BLOCK_M"]),
triton.cdiv(dim, meta["BLOCK_N"]),
)
_modulate_bwd[grid](dx, x, dy, scale, dscale, x.stride(0), scale.stride(0), M, N, seq_len)
dscale = torch.sum(dscale, dim=1)
return dx, dscale, dshift
def fused_modulate(
x: torch.Tensor,
scale: torch.Tensor,
shift: torch.Tensor,
) -> torch.Tensor:
return _FusedModulate.apply(x, scale, shift)
-306
View File
@@ -1,306 +0,0 @@
"""
This script demonstrates how to generate a video using the CogVideoX model with the Hugging Face `diffusers` pipeline.
The script supports different types of video generation, including text-to-video (t2v), image-to-video (i2v),
and video-to-video (v2v), depending on the input data and different weight.
- text-to-video: THUDM/CogVideoX-5b, THUDM/CogVideoX-2b or THUDM/CogVideoX1.5-5b
- video-to-video: THUDM/CogVideoX-5b, THUDM/CogVideoX-2b or THUDM/CogVideoX1.5-5b
- image-to-video: THUDM/CogVideoX-5b-I2V or THUDM/CogVideoX1.5-5b-I2V
Running the Script:
To run the script, use the following command with appropriate arguments:
```bash
$ python cli_demo.py --prompt "A girl riding a bike." --model_path THUDM/CogVideoX1.5-5b --generate_type "t2v"
```
Additional options are available to specify the model path, guidance scale, number of inference steps, video generation type, and output paths.
"""
import argparse
import logging
import os
from typing import Literal, Optional
import torch
from diffusers import (
CogVideoXDPMScheduler,
CogVideoXImageToVideoPipeline,
CogVideoXPipeline,
CogVideoXVideoToVideoPipeline,
)
from diffusers.utils import export_to_video, load_image, load_video
logging.basicConfig(level=logging.INFO)
# Recommended resolution for each model (width, height)
RESOLUTION_MAP = {
# cogvideox1.5-*
"cogvideox1.5-5b-i2v": (1360, 768),
"cogvideox1.5-5b": (1360, 768),
# cogvideox-*
"cogvideox-5b-i2v": (720, 480),
"cogvideox-5b": (720, 480),
"cogvideox-2b": (720, 480),
}
def generate_video(
prompt: str,
model_path: str,
lora_path: str = None,
lora_rank: int = 128,
num_frames: int = 81,
width: Optional[int] = None,
height: Optional[int] = None,
output_path: str = "./output.mp4",
image_or_video_path: str = "",
num_inference_steps: int = 50,
guidance_scale: float = 6.0,
num_videos_per_prompt: int = 1,
dtype: torch.dtype = torch.bfloat16,
generate_type: str = Literal[
"t2v", "i2v", "v2v"
], # i2v: image to video, v2v: video to video
seed: int = 42,
fps: int = 16,
):
"""
Generates a video based on the given prompt and saves it to the specified path.
Parameters:
- prompt (str): The description of the video to be generated.
- model_path (str): The path of the pre-trained model to be used.
- lora_path (str): The path of the LoRA weights to be used.
- lora_rank (int): The rank of the LoRA weights.
- output_path (str): The path where the generated video will be saved.
- num_inference_steps (int): Number of steps for the inference process. More steps can result in better quality.
- num_frames (int): Number of frames to generate. CogVideoX1.0 generates 49 frames for 6 seconds at 8 fps, while CogVideoX1.5 produces either 81 or 161 frames, corresponding to 5 seconds or 10 seconds at 16 fps.
- width (int): The width of the generated video, applicable only for CogVideoX1.5-5B-I2V
- height (int): The height of the generated video, applicable only for CogVideoX1.5-5B-I2V
- guidance_scale (float): The scale for classifier-free guidance. Higher values can lead to better alignment with the prompt.
- num_videos_per_prompt (int): Number of videos to generate per prompt.
- dtype (torch.dtype): The data type for computation (default is torch.bfloat16).
- generate_type (str): The type of video generation (e.g., 't2v', 'i2v', 'v2v').·
- seed (int): The seed for reproducibility.
- fps (int): The frames per second for the generated video.
"""
# 1. Load the pre-trained CogVideoX pipeline with the specified precision (bfloat16).
# add device_map="balanced" in the from_pretrained function and remove the enable_model_cpu_offload()
# function to use Multi GPUs.
image = None
video = None
model_name = model_path.split("/")[-1].lower()
desired_resolution = RESOLUTION_MAP[model_name]
if width is None or height is None:
width, height = desired_resolution
logging.info(
f"\033[1mUsing default resolution {desired_resolution} for {model_name}\033[0m"
)
elif (width, height) != desired_resolution:
if generate_type == "i2v":
# For i2v models, use user-defined width and height
logging.warning(
f"\033[1;31mThe width({width}) and height({height}) are not recommended for {model_name}. The best resolution is {desired_resolution}.\033[0m"
)
else:
# Otherwise, use the recommended width and height
logging.warning(
f"\033[1;31m{model_name} is not supported for custom resolution. Setting back to default resolution {desired_resolution}.\033[0m"
)
width, height = desired_resolution
if generate_type == "i2v":
pipe = CogVideoXImageToVideoPipeline.from_pretrained(
model_path, torch_dtype=dtype
)
image = load_image(image=image_or_video_path)
elif generate_type == "t2v":
pipe = CogVideoXPipeline.from_pretrained(model_path, torch_dtype=dtype)
else:
pipe = CogVideoXVideoToVideoPipeline.from_pretrained(
model_path, torch_dtype=dtype
)
video = load_video(image_or_video_path)
# If you're using with lora, add this code
if lora_path:
pipe.load_lora_weights(
lora_path,
weight_name="pytorch_lora_weights.safetensors",
adapter_name="test_1",
)
pipe.fuse_lora(lora_scale=1 / lora_rank)
# 2. Set Scheduler.
# Can be changed to `CogVideoXDPMScheduler` or `CogVideoXDDIMScheduler`.
# We recommend using `CogVideoXDDIMScheduler` for CogVideoX-2B.
# using `CogVideoXDPMScheduler` for CogVideoX-5B / CogVideoX-5B-I2V.
# pipe.scheduler = CogVideoXDDIMScheduler.from_config(pipe.scheduler.config, timestep_spacing="trailing")
pipe.scheduler = CogVideoXDPMScheduler.from_config(
pipe.scheduler.config, timestep_spacing="trailing"
)
# 3. Enable CPU offload for the model.
# turn off if you have multiple GPUs or enough GPU memory(such as H100) and it will cost less time in inference
# and enable to("cuda")
# pipe.to("cuda")
pipe.enable_sequential_cpu_offload()
pipe.vae.enable_slicing()
pipe.vae.enable_tiling()
# 4. Generate the video frames based on the prompt.
# `num_frames` is the Number of frames to generate.
if generate_type == "i2v":
video_generate = pipe(
height=height,
width=width,
prompt=prompt,
image=image,
# The path of the image, the resolution of video will be the same as the image for CogVideoX1.5-5B-I2V, otherwise it will be 720 * 480
num_videos_per_prompt=num_videos_per_prompt, # Number of videos to generate per prompt
num_inference_steps=num_inference_steps, # Number of inference steps
num_frames=num_frames, # Number of frames to generate
use_dynamic_cfg=True, # This id used for DPM scheduler, for DDIM scheduler, it should be False
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(
seed
), # Set the seed for reproducibility
).frames[0]
elif generate_type == "t2v":
video_generate = pipe(
height=height,
width=width,
prompt=prompt,
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
num_frames=num_frames,
use_dynamic_cfg=True,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed),
).frames[0]
else:
video_generate = pipe(
height=height,
width=width,
prompt=prompt,
video=video, # The path of the video to be used as the background of the video
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
num_frames=num_frames,
use_dynamic_cfg=True,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(
seed
), # Set the seed for reproducibility
).frames[0]
export_to_video(video_generate, output_path, fps=fps)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate a video from a text prompt using CogVideoX"
)
parser.add_argument(
"--prompt",
type=str,
required=True,
help="The description of the video to be generated",
)
parser.add_argument(
"--image_or_video_path",
type=str,
default=None,
help="The path of the image to be used as the background of the video",
)
parser.add_argument(
"--model_path",
type=str,
default="THUDM/CogVideoX1.5-5B",
help="Path of the pre-trained model use",
)
parser.add_argument(
"--lora_path",
type=str,
default=None,
help="The path of the LoRA weights to be used",
)
parser.add_argument(
"--lora_rank", type=int, default=128, help="The rank of the LoRA weights"
)
parser.add_argument(
"--output_path",
type=str,
default="./output.mp4",
help="The path save generated video",
)
parser.add_argument(
"--guidance_scale",
type=float,
default=6.0,
help="The scale for classifier-free guidance",
)
parser.add_argument(
"--num_inference_steps", type=int, default=50, help="Inference steps"
)
parser.add_argument(
"--num_frames",
type=int,
default=81,
help="Number of steps for the inference process",
)
parser.add_argument(
"--width", type=int, default=None, help="The width of the generated video"
)
parser.add_argument(
"--height", type=int, default=None, help="The height of the generated video"
)
parser.add_argument(
"--fps",
type=int,
default=16,
help="The frames per second for the generated video",
)
parser.add_argument(
"--num_videos_per_prompt",
type=int,
default=1,
help="Number of videos to generate per prompt",
)
parser.add_argument(
"--generate_type", type=str, default="t2v", help="The type of video generation"
)
parser.add_argument(
"--dtype", type=str, default="bfloat16", help="The data type for computation"
)
parser.add_argument(
"--seed", type=int, default=42, help="The seed for reproducibility"
)
args = parser.parse_args()
dtype = torch.float16 if args.dtype == "float16" else torch.bfloat16
os.makedirs(os.path.dirname(args.output_path), exist_ok=True)
generate_video(
prompt=args.prompt,
model_path=args.model_path,
lora_path=args.lora_path,
lora_rank=args.lora_rank,
output_path=args.output_path,
num_frames=args.num_frames,
width=args.width,
height=args.height,
image_or_video_path=args.image_or_video_path,
num_inference_steps=args.num_inference_steps,
guidance_scale=args.guidance_scale,
num_videos_per_prompt=args.num_videos_per_prompt,
dtype=dtype,
generate_type=args.generate_type,
seed=args.seed,
fps=args.fps,
)
+1
View File
@@ -50,6 +50,7 @@ def main(args):
prompts = f.readlines()
for prompt in prompts:
print("#####PROMPT#####", prompt)
outputs = hunyuan_video_sampler.predict(
prompt=prompt,
height=args.height,
+256
View File
File diff suppressed because one or more lines are too long
+681
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
num_gpus=1
export MODEL_BASE=data/hunyuan
torchrun --nnodes=1 --nproc_per_node=$num_gpus --master_port 29503 \
fastvideo/sample/sample_t2v_hunyuan.py \
--height 720 \
--width 720 \
--num_frames 61 \
--num_inference_steps 6 \
--guidance_scale 1 \
--embedded_cfg_scale 6 \
--flow_shift 17 \
--flow-reverse \
--prompt ./assets/prompt.txt \
--seed 1024 \
--output_path outputs_video/hunyuan/sw/ \
--model_path $MODEL_BASE \
--dit-weight ${MODEL_BASE}/hunyuan-video-t2v-720p/transformers/mp_rank_00_model_states.pt \
--vae-sp
-297
View File
@@ -1,297 +0,0 @@
# Copyright (c) 2023, Tri Dao.
"""Useful functions for writing test code."""
import torch
import torch.utils.benchmark as benchmark
def get_abs_err(x, y):
return (x - y).flatten().abs().max().item()
def get_err_ratio(x, y):
err = (x - y).flatten().square().mean().sqrt().item()
base = x.flatten().square().mean().sqrt().item()
return err / base
def assert_close(prefix, ref, tri, ratio):
msg = f"{prefix} diff: {get_abs_err(ref, tri):.6f} ratio: {get_err_ratio(ref, tri):.6f}"
print(msg)
assert get_err_ratio(ref, tri) < ratio, msg
def benchmark_forward(
fn,
*inputs,
repeats=10,
desc="",
verbose=True,
amp=False,
amp_dtype=torch.float16,
**kwinputs,
):
"""Use Pytorch Benchmark on the forward pass of an arbitrary function."""
if verbose:
print(desc, "- Forward pass")
def amp_wrapper(*inputs, **kwinputs):
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
fn(*inputs, **kwinputs)
t = benchmark.Timer(
stmt="fn_amp(*inputs, **kwinputs)",
globals={"fn_amp": amp_wrapper, "inputs": inputs, "kwinputs": kwinputs},
num_threads=torch.get_num_threads(),
)
m = t.timeit(repeats)
if verbose:
print(m)
return t, m
def benchmark_backward(
fn,
*inputs,
grad=None,
repeats=10,
desc="",
verbose=True,
amp=False,
amp_dtype=torch.float16,
**kwinputs,
):
"""Use Pytorch Benchmark on the backward pass of an arbitrary function."""
if verbose:
print(desc, "- Backward pass")
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
y = fn(*inputs, **kwinputs)
if type(y) is tuple:
y = y[0]
if grad is None:
grad = torch.randn_like(y)
else:
if grad.shape != y.shape:
raise RuntimeError("Grad shape does not match output shape")
def f(*inputs, y, grad):
# Set .grad to None to avoid extra operation of gradient accumulation
for x in inputs:
if isinstance(x, torch.Tensor):
x.grad = None
y.backward(grad, retain_graph=True)
t = benchmark.Timer(
stmt="f(*inputs, y=y, grad=grad)",
globals={"f": f, "inputs": inputs, "y": y, "grad": grad},
num_threads=torch.get_num_threads(),
)
m = t.timeit(repeats)
if verbose:
print(m)
return t, m
def benchmark_combined(
fn,
*inputs,
grad=None,
repeats=10,
desc="",
verbose=True,
amp=False,
amp_dtype=torch.float16,
**kwinputs,
):
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
if verbose:
print(desc, "- Forward + Backward pass")
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
y = fn(*inputs, **kwinputs)
if type(y) is tuple:
y = y[0]
if grad is None:
grad = torch.randn_like(y)
else:
if grad.shape != y.shape:
raise RuntimeError("Grad shape does not match output shape")
def f(grad, *inputs, **kwinputs):
for x in inputs:
if isinstance(x, torch.Tensor):
x.grad = None
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
y = fn(*inputs, **kwinputs)
if type(y) is tuple:
y = y[0]
y.backward(grad, retain_graph=True)
t = benchmark.Timer(
stmt="f(grad, *inputs, **kwinputs)",
globals={
"f": f,
"fn": fn,
"inputs": inputs,
"grad": grad,
"kwinputs": kwinputs,
},
num_threads=torch.get_num_threads(),
)
m = t.timeit(repeats)
if verbose:
print(m)
return t, m
def benchmark_fwd_bwd(
fn,
*inputs,
grad=None,
repeats=10,
desc="",
verbose=True,
amp=False,
amp_dtype=torch.float16,
**kwinputs,
):
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
return (
benchmark_forward(
fn,
*inputs,
repeats=repeats,
desc=desc,
verbose=verbose,
amp=amp,
amp_dtype=amp_dtype,
**kwinputs,
),
benchmark_backward(
fn,
*inputs,
grad=grad,
repeats=repeats,
desc=desc,
verbose=verbose,
amp=amp,
amp_dtype=amp_dtype,
**kwinputs,
),
)
def benchmark_all(
fn,
*inputs,
grad=None,
repeats=10,
desc="",
verbose=True,
amp=False,
amp_dtype=torch.float16,
**kwinputs,
):
"""Use Pytorch Benchmark on the forward+backward pass of an arbitrary function."""
return (
benchmark_forward(
fn,
*inputs,
repeats=repeats,
desc=desc,
verbose=verbose,
amp=amp,
amp_dtype=amp_dtype,
**kwinputs,
),
benchmark_backward(
fn,
*inputs,
grad=grad,
repeats=repeats,
desc=desc,
verbose=verbose,
amp=amp,
amp_dtype=amp_dtype,
**kwinputs,
),
benchmark_combined(
fn,
*inputs,
grad=grad,
repeats=repeats,
desc=desc,
verbose=verbose,
amp=amp,
amp_dtype=amp_dtype,
**kwinputs,
),
)
def pytorch_profiler(
fn,
*inputs,
trace_filename=None,
backward=False,
amp=False,
amp_dtype=torch.float16,
cpu=False,
verbose=True,
**kwinputs,
):
"""Wrap benchmark functions in Pytorch profiler to see CUDA information."""
if backward:
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
out = fn(*inputs, **kwinputs)
if type(out) is tuple:
out = out[0]
g = torch.randn_like(out)
for _ in range(30): # Warm up
if backward:
for x in inputs:
if isinstance(x, torch.Tensor):
x.grad = None
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
out = fn(*inputs, **kwinputs)
if type(out) is tuple:
out = out[0]
# Backward should be done outside autocast
if backward:
out.backward(g, retain_graph=True)
activities = ([torch.profiler.ProfilerActivity.CPU] if cpu else []) + [
torch.profiler.ProfilerActivity.CUDA
]
with torch.profiler.profile(
activities=activities,
record_shapes=True,
# profile_memory=True,
with_stack=True,
) as prof:
if backward:
for x in inputs:
if isinstance(x, torch.Tensor):
x.grad = None
with torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=amp):
out = fn(*inputs, **kwinputs)
if type(out) is tuple:
out = out[0]
if backward:
out.backward(g, retain_graph=True)
if verbose:
# print(prof.key_averages().table(sort_by="self_cuda_time_total", row_limit=50))
print(prof.key_averages().table(row_limit=50))
if trace_filename is not None:
prof.export_chrome_trace(trace_filename)
def benchmark_memory(fn, *inputs, desc="", verbose=True, **kwinputs):
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
torch.cuda.synchronize()
fn(*inputs, **kwinputs)
torch.cuda.synchronize()
mem = torch.cuda.max_memory_allocated() / ((2**20) * 1000)
if verbose:
print(f"{desc} max memory: {mem}GB")
torch.cuda.empty_cache()
return mem
-83
View File
@@ -1,83 +0,0 @@
import torch
from torch import nn
from benchmark import (
benchmark_combined,
benchmark_forward,
benchmark_backward,
assert_close,
)
from fastvideo.ops.modulate.modulate import fused_modulate
def torch_modulate(x, scale, shift):
return x * (scale.unsqueeze(1) + 1) + shift.unsqueeze(1)
# from flash_attn import flash_attn_func
def time_fwd(func, *args, **kwargs):
time_fb = benchmark_forward(func, *args, **kwargs)
return time_fb[1].mean
def time_fwd_bwd(func, *args, **kwargs):
time_fb = benchmark_combined(func, *args, **kwargs)
return time_fb[1].mean
def time_bwd(func, *args, **kwargs):
time_fb = benchmark_backward(func, *args, **kwargs)
return time_fb[1].mean
device = "cuda"
dtype = torch.bfloat16
batch_sizes = [1, 8, 32]
seq_lengths = [128, 512, 1024]
hidden_dims = [768, 1024, 2048]
# methods = (["torch", "triton", "thunderkitten"])
methods = ["torch", "triton"]
time_f = {}
time_b = {}
time_f_b = {}
speed_f = {}
speed_b = {}
speed_f_b = {}
for B in batch_sizes:
for T in seq_lengths:
for D in hidden_dims:
config = (B, T, D)
norm_func = nn.LayerNorm(D, elementwise_affine=False, eps=1e-6)
x = torch.randn(B, T, D, device="cuda", requires_grad=True, dtype=dtype)
x = norm_func(x.to(torch.float32)).to(dtype)
shift = torch.randn(B, D, device="cuda", requires_grad=True, dtype=dtype)
scale = torch.randn(B, D, device="cuda", requires_grad=True, dtype=dtype)
# test torch
o_ref = torch_modulate(x, scale, shift)
o_ref.sum().backward(retain_graph=True)
f_b = time_fwd_bwd(torch_modulate, x, scale, shift, verbose=False)
time_f_b[config, "torch"] = f_b
# test triton
o2 = fused_modulate(x, scale, shift)
o2.sum().backward(retain_graph=True)
f_b = time_fwd_bwd(fused_modulate, x, scale, shift, verbose=False)
time_f_b[config, "triton"] = f_b
# test if the results are close
assert_close(" o", o_ref, o2, 0.005)
# time_f_b[config, "thunderkitten"] = f_b
print(f"### batch size={B}, seq length={T}, B={B}, hidden dim={D} ###")
for method in methods:
# time_f_b[config, method] = time_f[config, method] + time_b[config, method]
print(
f"{method:>50} fwd + bwd:\t {time_f_b[config, method]*1000:>6.4f} ms "
)
# with open('flash2_attn_time.plk', 'wb') as fp:
# pickle.dump((speed_f, speed_b, speed_f_b), fp, protocol=pickle.HIGHEST_PROTOCOL)