Merge branch 'main' of github.com:hao-ai-lab/FastVideo
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository_owner == 'hao-ai-lab' }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@v1
|
||||
with:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -310,7 +310,7 @@
|
||||
"value": -99999,
|
||||
"cachedValue": "fp16"
|
||||
},
|
||||
"use_cpu_offload": {
|
||||
"dit_cpu_offload": {
|
||||
"isAuto": true,
|
||||
"value": -99999,
|
||||
"cachedValue": true
|
||||
|
||||
@@ -517,7 +517,7 @@
|
||||
"value": "fp16",
|
||||
"cachedValue": "fp16"
|
||||
},
|
||||
"use_cpu_offload": {
|
||||
"dit_cpu_offload": {
|
||||
"isAuto": true,
|
||||
"value": true,
|
||||
"cachedValue": true
|
||||
|
||||
@@ -105,7 +105,7 @@ class VideoGenerator:
|
||||
"precision": (["fp16", "bf16"], {
|
||||
"default": "fp16"
|
||||
}),
|
||||
"use_cpu_offload": ([True, False], {
|
||||
"dit_cpu_offload": ([True, False], {
|
||||
"default": False
|
||||
}),
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class VideoGenerator:
|
||||
vae_config=None,
|
||||
text_encoder_config=None,
|
||||
dit_config=None,
|
||||
use_cpu_offload=None,
|
||||
dit_cpu_offload=None,
|
||||
):
|
||||
print('Running FastVideo inference')
|
||||
|
||||
@@ -259,8 +259,8 @@ class VideoGenerator:
|
||||
raw_generation_args['tp_size'] = tp_size
|
||||
if sp_size is not None:
|
||||
raw_generation_args['sp_size'] = sp_size
|
||||
if use_cpu_offload is not None:
|
||||
raw_generation_args['use_cpu_offload'] = use_cpu_offload
|
||||
if dit_cpu_offload is not None:
|
||||
raw_generation_args['dit_cpu_offload'] = dit_cpu_offload
|
||||
|
||||
generation_args = {
|
||||
k: v
|
||||
|
||||
@@ -552,7 +552,7 @@ app.registerExtension({
|
||||
]
|
||||
const floatWidgetNames = ["embedded_cfg_scale", "guidance_scale"]
|
||||
const comboWidgetNames = ["vae_tiling", "vae_precision", "vae_sp", "text_encoder_precision", "precision",
|
||||
"load_encoder", "load_decoder", "use_tiling", "use_temporal_tiling", "use_parallel_tiling", "use_cpu_offload", "enable_teacache"
|
||||
"load_encoder", "load_decoder", "use_tiling", "use_temporal_tiling", "use_parallel_tiling", "dit_cpu_offload", "enable_teacache"
|
||||
]
|
||||
const stringWidgetNames = ["prefix", "quant_config", "lora_config", "image_path"]
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def main():
|
||||
model_name = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
|
||||
config = PipelineConfig.from_pretrained(model_name)
|
||||
config.vae_precision = "fp16"
|
||||
config.use_cpu_offload = True
|
||||
config.dit_cpu_offload = True
|
||||
|
||||
# Create the generator
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
|
||||
@@ -13,7 +13,10 @@ def main():
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=2,
|
||||
use_fsdp_inference=True,
|
||||
use_cpu_offload=False
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
# image_encoder_cpu_offload=False,
|
||||
)
|
||||
|
||||
# sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
|
||||
@@ -16,8 +16,9 @@ def main():
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
# Adjust these offload parameters if you have < 32GB of VRAM
|
||||
text_encoder_offload=False,
|
||||
use_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
VSA_sparsity=0.8,
|
||||
)
|
||||
load_end_time = time.perf_counter()
|
||||
@@ -35,11 +36,24 @@ def main():
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
|
||||
end_time = time.perf_counter()
|
||||
gen_time = end_time - start_time
|
||||
e2e_gen_time = end_time - start_time
|
||||
|
||||
# Generate another video with a different prompt, without reloading the
|
||||
# model!
|
||||
prompt2 = (
|
||||
"A majestic lion strides across the golden savanna, its powerful frame "
|
||||
"glistening under the warm afternoon sun. The tall grass ripples gently in "
|
||||
"the breeze, enhancing the lion's commanding presence. The tone is vibrant, "
|
||||
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
|
||||
"cinematic.")
|
||||
start_time = time.perf_counter()
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
|
||||
end_time = time.perf_counter()
|
||||
gen_time2 = end_time - start_time
|
||||
|
||||
|
||||
print(f"Time taken to load model: {load_time} seconds")
|
||||
print(f"Time taken to generate video: {gen_time} seconds")
|
||||
print(f"Time taken for e2e generation: {e2e_gen_time} seconds")
|
||||
print(f"Time taken to generate video2: {gen_time2} seconds")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -9,8 +9,8 @@ def main():
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
pipeline_config=config,
|
||||
use_fsdp_inference=False, # Disable FSDP for MPS
|
||||
use_cpu_offload=True,
|
||||
text_encoder_offload=True,
|
||||
dit_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
disable_autocast=False,
|
||||
num_gpus=1,
|
||||
|
||||
@@ -16,8 +16,8 @@ def main():
|
||||
# if num_gpus > 1, FastVideo will automatically handle distributed setup
|
||||
pipeline_config=pipeline_config,
|
||||
use_fsdp_inference=False, # Disable FSDP for MPS
|
||||
use_cpu_offload=True,
|
||||
text_encoder_offload=True,
|
||||
dit_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
disable_autocast=False,
|
||||
num_gpus=1,
|
||||
|
||||
@@ -9,7 +9,7 @@ def main():
|
||||
gen = VideoGenerator.from_pretrained(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
num_gpus=1,
|
||||
use_cpu_offload=False,
|
||||
dit_cpu_offload=False,
|
||||
)
|
||||
load_time = time.perf_counter() - start_time
|
||||
print(f"Model loading time: {load_time:.2f} seconds")
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import dataclasses
|
||||
from typing import Any, Optional
|
||||
|
||||
from fastvideo.configs.utils import update_config_from_args
|
||||
from fastvideo.utils import FlexibleArgumentParser, StoreBoolean
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PreprocessConfig:
|
||||
"""Configuration for preprocessing operations."""
|
||||
|
||||
# Model and dataset configuration
|
||||
model_path: str = ""
|
||||
dataset_path: str = ""
|
||||
dataset_output_dir: str = "./output"
|
||||
|
||||
# Dataloader configuration
|
||||
dataloader_num_workers: int = 1
|
||||
preprocess_video_batch_size: int = 2
|
||||
|
||||
# Saver configuration
|
||||
samples_per_file: int = 64
|
||||
flush_frequency: int = 256
|
||||
|
||||
# Video processing parameters
|
||||
max_height: int = 480
|
||||
max_width: int = 848
|
||||
num_frames: int = 163
|
||||
video_length_tolerance_range: float = 2.0
|
||||
train_fps: int = 30
|
||||
speed_factor: float = 1.0
|
||||
drop_short_ratio: float = 1.0
|
||||
do_temporal_sample: bool = False
|
||||
|
||||
# Model configuration
|
||||
training_cfg_rate: float = 0.0
|
||||
|
||||
@staticmethod
|
||||
def add_cli_args(parser: FlexibleArgumentParser,
|
||||
prefix: str = "preprocess") -> FlexibleArgumentParser:
|
||||
"""Add preprocessing configuration arguments to the parser."""
|
||||
prefix_with_dot = f"{prefix}." if (prefix.strip() != "") else ""
|
||||
|
||||
preprocess_args = parser.add_argument_group("Preprocessing Arguments")
|
||||
# Model & Dataset
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}model-path",
|
||||
type=str,
|
||||
default=PreprocessConfig.model_path,
|
||||
help="Path to the model for preprocessing")
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}dataset-path",
|
||||
type=str,
|
||||
default=PreprocessConfig.dataset_path,
|
||||
help="Path to the dataset directory for preprocessing")
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}dataset-output-dir",
|
||||
type=str,
|
||||
default=PreprocessConfig.dataset_output_dir,
|
||||
help="The output directory where the dataset will be written.")
|
||||
|
||||
# Dataloader
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}dataloader-num-workers",
|
||||
type=int,
|
||||
default=PreprocessConfig.dataloader_num_workers,
|
||||
help=
|
||||
"Number of subprocesses to use for data loading. 0 means that the data will be loaded in the main process."
|
||||
)
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}preprocess-video-batch-size",
|
||||
type=int,
|
||||
default=PreprocessConfig.preprocess_video_batch_size,
|
||||
help="Batch size (per device) for the training dataloader.")
|
||||
|
||||
# Saver
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}samples-per-file",
|
||||
type=int,
|
||||
default=PreprocessConfig.samples_per_file,
|
||||
help="Number of samples per output file")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}flush-frequency",
|
||||
type=int,
|
||||
default=PreprocessConfig.flush_frequency,
|
||||
help="How often to save to parquet files")
|
||||
|
||||
# Video processing parameters
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}max-height",
|
||||
type=int,
|
||||
default=PreprocessConfig.max_height,
|
||||
help="Maximum height for video processing")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}max-width",
|
||||
type=int,
|
||||
default=PreprocessConfig.max_width,
|
||||
help="Maximum width for video processing")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}num-frames",
|
||||
type=int,
|
||||
default=PreprocessConfig.num_frames,
|
||||
help="Number of frames to process")
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}video-length-tolerance-range",
|
||||
type=float,
|
||||
default=PreprocessConfig.video_length_tolerance_range,
|
||||
help="Video length tolerance range")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}train-fps",
|
||||
type=int,
|
||||
default=PreprocessConfig.train_fps,
|
||||
help="Training FPS")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}speed-factor",
|
||||
type=float,
|
||||
default=PreprocessConfig.speed_factor,
|
||||
help="Speed factor for video processing")
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}drop-short-ratio",
|
||||
type=float,
|
||||
default=PreprocessConfig.drop_short_ratio,
|
||||
help="Ratio for dropping short videos")
|
||||
preprocess_args.add_argument(
|
||||
f"--{prefix_with_dot}do-temporal-sample",
|
||||
action=StoreBoolean,
|
||||
default=PreprocessConfig.do_temporal_sample,
|
||||
help="Whether to do temporal sampling")
|
||||
|
||||
# Model Training configuration
|
||||
preprocess_args.add_argument(f"--{prefix_with_dot}training-cfg-rate",
|
||||
type=float,
|
||||
default=PreprocessConfig.training_cfg_rate,
|
||||
help="Training CFG rate")
|
||||
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
def from_kwargs(cls, kwargs: dict[str,
|
||||
Any]) -> Optional["PreprocessConfig"]:
|
||||
"""Create PreprocessConfig from keyword arguments."""
|
||||
preprocess_config = cls()
|
||||
if not update_config_from_args(
|
||||
preprocess_config, kwargs, prefix="preprocess", pop_args=True):
|
||||
return None
|
||||
return preprocess_config
|
||||
|
||||
def check_preprocess_config(self) -> None:
|
||||
if self.dataset_path == "":
|
||||
raise ValueError("dataset_path must be set for preprocessing mode")
|
||||
if self.samples_per_file <= 0:
|
||||
raise ValueError("samples_per_file must be greater than 0")
|
||||
if self.flush_frequency <= 0:
|
||||
raise ValueError("flush_frequency must be greater than 0")
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"embedded_cfg_scale": 6,
|
||||
"flow_shift": 17,
|
||||
"use_cpu_offload": false,
|
||||
"dit_cpu_offload": false,
|
||||
"disable_autocast": false,
|
||||
"precision": "bf16",
|
||||
"vae_precision": "fp32",
|
||||
|
||||
@@ -23,6 +23,7 @@ class SamplingParam:
|
||||
negative_prompt: str | None = None
|
||||
prompt_path: str | None = None
|
||||
output_path: str = "outputs/"
|
||||
output_video_name: str | None = None
|
||||
|
||||
# Batch info
|
||||
num_videos_per_prompt: int = 1
|
||||
@@ -106,6 +107,12 @@ class SamplingParam:
|
||||
default=SamplingParam.output_path,
|
||||
help="Path to save the generated video",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-video-name",
|
||||
type=str,
|
||||
default=SamplingParam.output_video_name,
|
||||
help="Name of the output video",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--num-videos-per-prompt",
|
||||
type=int,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import argparse
|
||||
from typing import Any
|
||||
|
||||
|
||||
def update_config_from_args(config: Any,
|
||||
args_dict: dict[str, Any],
|
||||
prefix: str = "",
|
||||
pop_args: bool = False) -> None:
|
||||
pop_args: bool = False) -> bool:
|
||||
"""
|
||||
Update configuration object from arguments dictionary.
|
||||
|
||||
@@ -43,3 +44,18 @@ def update_config_from_args(config: Any,
|
||||
for key in args_to_remove:
|
||||
if key not in args_not_to_remove:
|
||||
args_dict.pop(key)
|
||||
|
||||
return len(args_to_remove) > 0
|
||||
|
||||
|
||||
def clean_cli_args(args: argparse.Namespace) -> dict[str, Any]:
|
||||
"""
|
||||
Clean the arguments by removing the ones that not explicitly provided by the user.
|
||||
"""
|
||||
provided_args = {}
|
||||
for k, v in vars(args).items():
|
||||
if (v is not None and hasattr(args, '_provided')
|
||||
and k in args._provided):
|
||||
provided_args[k] = v
|
||||
|
||||
return provided_args
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"embedded_cfg_scale": 6.0,
|
||||
"flow_shift": 3,
|
||||
"use_cpu_offload": true,
|
||||
"dit_cpu_offload": true,
|
||||
"disable_autocast": false,
|
||||
"precision": "bf16",
|
||||
"vae_precision": "fp32",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"embedded_cfg_scale": 6.0,
|
||||
"flow_shift": 3,
|
||||
"use_cpu_offload": true,
|
||||
"dit_cpu_offload": true,
|
||||
"disable_autocast": false,
|
||||
"precision": "bf16",
|
||||
"vae_precision": "fp32",
|
||||
|
||||
@@ -58,9 +58,18 @@ class GenerateSubcommand(CLISubcommand):
|
||||
"model_path must be provided either in config file or via --model-path"
|
||||
)
|
||||
|
||||
if 'prompt' not in merged_args or not merged_args['prompt']:
|
||||
# Check if either prompt or prompt_txt is provided
|
||||
has_prompt = 'prompt' in merged_args and merged_args['prompt']
|
||||
has_prompt_txt = 'prompt_txt' in merged_args and merged_args[
|
||||
'prompt_txt']
|
||||
|
||||
if not (has_prompt or has_prompt_txt):
|
||||
raise ValueError("Either prompt or prompt_txt must be provided")
|
||||
|
||||
if has_prompt and has_prompt_txt:
|
||||
raise ValueError(
|
||||
"prompt must be provided either in config file or via --prompt")
|
||||
"Cannot provide both 'prompt' and 'prompt_txt'. Use only one of them."
|
||||
)
|
||||
|
||||
init_args = {
|
||||
k: v
|
||||
@@ -73,11 +82,12 @@ class GenerateSubcommand(CLISubcommand):
|
||||
}
|
||||
|
||||
model_path = init_args.pop('model_path')
|
||||
prompt = generation_args.pop('prompt')
|
||||
prompt = generation_args.pop('prompt', None)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(model_path=model_path,
|
||||
**init_args)
|
||||
|
||||
# Call generate_video - it handles both single and batch modes
|
||||
generator.generate_video(prompt=prompt, **generation_args)
|
||||
|
||||
def validate(self, args: argparse.Namespace) -> None:
|
||||
|
||||
@@ -98,15 +98,15 @@ class VideoGenerator:
|
||||
|
||||
def generate_video(
|
||||
self,
|
||||
prompt: str,
|
||||
prompt: str | None = None,
|
||||
sampling_param: SamplingParam | None = None,
|
||||
**kwargs,
|
||||
) -> dict[str, Any] | list[np.ndarray]:
|
||||
) -> dict[str, Any] | list[np.ndarray] | list[dict[str, Any]]:
|
||||
"""
|
||||
Generate a video based on the given prompt.
|
||||
|
||||
Args:
|
||||
prompt: The prompt to use for generation
|
||||
prompt: The prompt to use for generation (optional if prompt_txt is provided)
|
||||
negative_prompt: The negative prompt to use (overrides the one in fastvideo_args)
|
||||
output_path: Path to save the video (overrides the one in fastvideo_args)
|
||||
output_video_name: Name of the video file to save. Default is the first 100 characters of the prompt.
|
||||
@@ -123,8 +123,73 @@ class VideoGenerator:
|
||||
callback_steps: Number of steps between each callback
|
||||
|
||||
Returns:
|
||||
Either the output dictionary or the list of frames depending on return_frames
|
||||
Either the output dictionary, list of frames, or list of results for batch processing
|
||||
"""
|
||||
# Handle batch processing from text file
|
||||
if self.fastvideo_args.prompt_txt is not None:
|
||||
prompt_txt_path = self.fastvideo_args.prompt_txt
|
||||
if not os.path.exists(prompt_txt_path):
|
||||
raise FileNotFoundError(
|
||||
f"Prompt text file not found: {prompt_txt_path}")
|
||||
|
||||
# Read prompts from file
|
||||
with open(prompt_txt_path, encoding='utf-8') as f:
|
||||
prompts = [line.strip() for line in f if line.strip()]
|
||||
|
||||
if not prompts:
|
||||
raise ValueError(f"No prompts found in file: {prompt_txt_path}")
|
||||
|
||||
logger.info("Found %d prompts in %s", len(prompts), prompt_txt_path)
|
||||
|
||||
if sampling_param is not None:
|
||||
original_output_video_name = sampling_param.output_video_name
|
||||
else:
|
||||
original_output_video_name = None
|
||||
|
||||
results = []
|
||||
for i, batch_prompt in enumerate(prompts):
|
||||
logger.info("Processing prompt %d/%d: %s...", i + 1,
|
||||
len(prompts), batch_prompt[:100])
|
||||
|
||||
try:
|
||||
# Generate video for this prompt using the same logic below
|
||||
if sampling_param is not None and original_output_video_name is not None:
|
||||
sampling_param.output_video_name = original_output_video_name + f"_{i}"
|
||||
result = self._generate_single_video(
|
||||
batch_prompt, sampling_param, **kwargs)
|
||||
|
||||
# Add prompt info to result
|
||||
if isinstance(result, dict):
|
||||
result["prompt_index"] = i
|
||||
result["prompt"] = batch_prompt
|
||||
|
||||
results.append(result)
|
||||
logger.info("Successfully generated video for prompt %d",
|
||||
i + 1)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to generate video for prompt %d: %s",
|
||||
i + 1, e)
|
||||
continue
|
||||
|
||||
logger.info(
|
||||
"Completed batch processing. Generated %d videos successfully.",
|
||||
len(results))
|
||||
return results
|
||||
|
||||
# Single prompt generation (original behavior)
|
||||
if prompt is None:
|
||||
raise ValueError("Either prompt or prompt_txt must be provided")
|
||||
|
||||
return self._generate_single_video(prompt, sampling_param, **kwargs)
|
||||
|
||||
def _generate_single_video(
|
||||
self,
|
||||
prompt: str,
|
||||
sampling_param: SamplingParam | None = None,
|
||||
**kwargs,
|
||||
) -> dict[str, Any] | list[np.ndarray]:
|
||||
"""Internal method for single video generation"""
|
||||
# Create a copy of inference args to avoid modifying the original
|
||||
fastvideo_args = self.fastvideo_args
|
||||
pipeline_config = fastvideo_args.pipeline_config
|
||||
@@ -222,6 +287,11 @@ class VideoGenerator:
|
||||
output_path: {sampling_param.output_path}
|
||||
""" # type: ignore[attr-defined]
|
||||
logger.info(debug_str)
|
||||
|
||||
# Use prompt[:100] for video name
|
||||
if sampling_param.output_video_name is None:
|
||||
sampling_param.output_video_name = prompt[:100]
|
||||
|
||||
# Prepare batch
|
||||
batch = ForwardBatch(
|
||||
**shallow_asdict(sampling_param),
|
||||
@@ -229,7 +299,6 @@ class VideoGenerator:
|
||||
n_tokens=n_tokens,
|
||||
VSA_sparsity=fastvideo_args.VSA_sparsity,
|
||||
extra={},
|
||||
output_video_name=kwargs.get("output_video_name", prompt[:100]),
|
||||
)
|
||||
|
||||
# Run inference
|
||||
|
||||
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
||||
CMAKE_BUILD_TYPE: str | None = None
|
||||
VERBOSE: bool = False
|
||||
FASTVIDEO_SERVER_DEV_MODE: bool = False
|
||||
FASTVIDEO_STAGE_LOGGING: bool = False
|
||||
|
||||
|
||||
def get_default_cache_root() -> str:
|
||||
@@ -172,6 +173,7 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
||||
# - "TORCH_SDPA": use torch.nn.MultiheadAttention
|
||||
# - "FLASH_ATTN": use FlashAttention
|
||||
# - "SLIDING_TILE_ATTN" : use Sliding Tile Attention
|
||||
# - "VIDEO_SPARSE_ATTN": use Video Sparse Attention
|
||||
# - "SAGE_ATTN": use Sage Attention
|
||||
"FASTVIDEO_ATTENTION_BACKEND":
|
||||
lambda: os.getenv("FASTVIDEO_ATTENTION_BACKEND", None),
|
||||
@@ -199,6 +201,11 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
||||
# e.g. `/reset_prefix_cache`
|
||||
"FASTVIDEO_SERVER_DEV_MODE":
|
||||
lambda: bool(int(os.getenv("FASTVIDEO_SERVER_DEV_MODE", "0"))),
|
||||
|
||||
# If set, fastvideo will enable stage logging, which will print the time
|
||||
# taken for each stage
|
||||
"FASTVIDEO_STAGE_LOGGING":
|
||||
lambda: bool(int(os.getenv("FASTVIDEO_STAGE_LOGGING", "0"))),
|
||||
}
|
||||
|
||||
# end-env-vars-definition
|
||||
|
||||
+190
-13
@@ -6,9 +6,12 @@ import argparse
|
||||
import dataclasses
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import field
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from fastvideo.configs.configs import PreprocessConfig
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig, STA_Mode
|
||||
from fastvideo.configs.utils import clean_cli_args
|
||||
from fastvideo.logger import init_logger
|
||||
from fastvideo.platforms import current_platform
|
||||
from fastvideo.utils import FlexibleArgumentParser, StoreBoolean
|
||||
@@ -16,17 +19,58 @@ from fastvideo.utils import FlexibleArgumentParser, StoreBoolean
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
def clean_cli_args(args: argparse.Namespace) -> dict[str, Any]:
|
||||
class ExecutionMode(str, Enum):
|
||||
"""
|
||||
Clean the arguments by removing the ones that not explicitly provided by the user.
|
||||
Enumeration for different pipeline modes.
|
||||
|
||||
Inherits from str to allow string comparison for backward compatibility.
|
||||
"""
|
||||
provided_args = {}
|
||||
for k, v in vars(args).items():
|
||||
if (v is not None and hasattr(args, '_provided')
|
||||
and k in args._provided):
|
||||
provided_args[k] = v
|
||||
INFERENCE = "inference"
|
||||
PREPROCESSING = "preprocessing"
|
||||
FINETUNING = "finetuning"
|
||||
DISTILLATION = "distillation"
|
||||
|
||||
return provided_args
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> "ExecutionMode":
|
||||
"""Convert string to ExecutionMode enum."""
|
||||
try:
|
||||
return cls(value.lower())
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Invalid mode: {value}. Must be one of: {', '.join([m.value for m in cls])}"
|
||||
) from None
|
||||
|
||||
@classmethod
|
||||
def choices(cls) -> list[str]:
|
||||
"""Get all available choices as strings for argparse."""
|
||||
return [mode.value for mode in cls]
|
||||
|
||||
|
||||
class WorkloadType(str, Enum):
|
||||
"""
|
||||
Enumeration for different workload types.
|
||||
|
||||
Inherits from str to allow string comparison for backward compatibility.
|
||||
"""
|
||||
I2V = "i2v" # Image to Video
|
||||
T2V = "t2v" # Text to Video
|
||||
T2I = "t2i" # Text to Image
|
||||
I2I = "i2i" # Image to Image
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, value: str) -> "WorkloadType":
|
||||
"""Convert string to WorkloadType enum."""
|
||||
try:
|
||||
return cls(value.lower())
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Invalid workload type: {value}. Must be one of: {', '.join([m.value for m in cls])}"
|
||||
) from None
|
||||
|
||||
@classmethod
|
||||
def choices(cls) -> list[str]:
|
||||
"""Get all available choices as strings for argparse."""
|
||||
return [workload.value for workload in cls]
|
||||
|
||||
|
||||
# args for fastvideo framework
|
||||
@@ -35,6 +79,12 @@ class FastVideoArgs:
|
||||
# Model and path configuration (for convenience)
|
||||
model_path: str
|
||||
|
||||
# Running mode
|
||||
mode: ExecutionMode = ExecutionMode.INFERENCE
|
||||
|
||||
# Workload type
|
||||
workload_type: WorkloadType = WorkloadType.T2V
|
||||
|
||||
# Cache strategy
|
||||
cache_strategy: str = "none"
|
||||
|
||||
@@ -56,6 +106,7 @@ class FastVideoArgs:
|
||||
dist_timeout: int | None = None # timeout for torch.distributed
|
||||
|
||||
pipeline_config: PipelineConfig = field(default_factory=PipelineConfig)
|
||||
preprocess_config: PreprocessConfig | None = None
|
||||
|
||||
# LoRA parameters
|
||||
# (Wenxuan) prefer to keep it here instead of in pipeline config to not make it complicated.
|
||||
@@ -67,9 +118,12 @@ class FastVideoArgs:
|
||||
|
||||
output_type: str = "pil"
|
||||
|
||||
use_cpu_offload: bool = True # For DiT
|
||||
# CPU offload parameters
|
||||
dit_cpu_offload: bool = True
|
||||
use_fsdp_inference: bool = True
|
||||
text_encoder_offload: bool = True
|
||||
text_encoder_cpu_offload: bool = True
|
||||
image_encoder_cpu_offload: bool = True
|
||||
vae_cpu_offload: bool = True
|
||||
pin_cpu_memory: bool = True
|
||||
|
||||
# STA (Sliding Tile Attention) parameters
|
||||
@@ -88,6 +142,9 @@ class FastVideoArgs:
|
||||
# Stage verification
|
||||
enable_stage_verification: bool = True
|
||||
|
||||
# Prompt text file for batch processing
|
||||
prompt_txt: str | None = None
|
||||
|
||||
# model paths for correct deallocation
|
||||
model_paths: dict[str, str] = field(default_factory=dict)
|
||||
model_loaded: dict[str, bool] = field(default_factory=lambda: {
|
||||
@@ -120,6 +177,24 @@ class FastVideoArgs:
|
||||
help="Directory containing StepVideo model",
|
||||
)
|
||||
|
||||
# Running mode
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
type=str,
|
||||
choices=ExecutionMode.choices(),
|
||||
default=FastVideoArgs.mode.value,
|
||||
help="The mode to run FastVideo",
|
||||
)
|
||||
|
||||
# Workload type
|
||||
parser.add_argument(
|
||||
"--workload-type",
|
||||
type=str,
|
||||
choices=WorkloadType.choices(),
|
||||
default=FastVideoArgs.workload_type.value,
|
||||
help="The workload type",
|
||||
)
|
||||
|
||||
# distributed_executor_backend
|
||||
parser.add_argument(
|
||||
"--distributed-executor-backend",
|
||||
@@ -198,6 +273,15 @@ class FastVideoArgs:
|
||||
help="Output type for the generated video",
|
||||
)
|
||||
|
||||
# Prompt text file for batch processing
|
||||
parser.add_argument(
|
||||
"--prompt-txt",
|
||||
type=str,
|
||||
default=FastVideoArgs.prompt_txt,
|
||||
help=
|
||||
"Path to a text file containing prompts (one per line) for batch processing",
|
||||
)
|
||||
|
||||
# STA (Sliding Tile Attention) parameters
|
||||
parser.add_argument(
|
||||
"--STA-mode",
|
||||
@@ -226,7 +310,7 @@ class FastVideoArgs:
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--use-cpu-offload",
|
||||
"--dit-cpu-offload",
|
||||
action=StoreBoolean,
|
||||
help=
|
||||
"Use CPU offload for DiT inference. Enable if run out of memory with FSDP.",
|
||||
@@ -243,6 +327,17 @@ class FastVideoArgs:
|
||||
help=
|
||||
"Use CPU offload for text encoder. Enable if run out of memory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--image-encoder-cpu-offload",
|
||||
action=StoreBoolean,
|
||||
help=
|
||||
"Use CPU offload for image encoder. Enable if run out of memory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vae-cpu-offload",
|
||||
action=StoreBoolean,
|
||||
help="Use CPU offload for VAE. Enable if run out of memory.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pin-cpu-memory",
|
||||
action=StoreBoolean,
|
||||
@@ -276,6 +371,9 @@ class FastVideoArgs:
|
||||
# Add pipeline configuration arguments
|
||||
PipelineConfig.add_cli_args(parser)
|
||||
|
||||
# Add preprocessing configuration arguments
|
||||
PreprocessConfig.add_cli_args(parser)
|
||||
|
||||
return parser
|
||||
|
||||
@classmethod
|
||||
@@ -285,11 +383,26 @@ class FastVideoArgs:
|
||||
attrs = [attr.name for attr in dataclasses.fields(cls)]
|
||||
|
||||
# Create a dictionary of attribute values, with defaults for missing attributes
|
||||
kwargs = {}
|
||||
kwargs: dict[str, Any] = {}
|
||||
for attr in attrs:
|
||||
if attr == 'pipeline_config':
|
||||
pipeline_config = PipelineConfig.from_kwargs(provided_args)
|
||||
kwargs[attr] = pipeline_config
|
||||
kwargs['pipeline_config'] = pipeline_config
|
||||
elif attr == 'preprocess_config':
|
||||
preprocess_config = PreprocessConfig.from_kwargs(provided_args)
|
||||
kwargs['preprocess_config'] = preprocess_config
|
||||
elif attr == 'mode':
|
||||
# Convert string to ExecutionMode enum
|
||||
mode_value = getattr(args, attr, FastVideoArgs.mode.value)
|
||||
kwargs['mode'] = ExecutionMode.from_string(
|
||||
mode_value) if isinstance(mode_value, str) else mode_value
|
||||
elif attr == 'workload_type':
|
||||
# Convert string to WorkloadType enum
|
||||
workload_type_value = getattr(args, 'workload_type',
|
||||
FastVideoArgs.workload_type.value)
|
||||
kwargs['workload_type'] = WorkloadType.from_string(
|
||||
workload_type_value) if isinstance(
|
||||
workload_type_value, str) else workload_type_value
|
||||
# Use getattr with default value from the dataclass for potentially missing attributes
|
||||
else:
|
||||
# Get the field to check if it has a default_factory
|
||||
@@ -308,7 +421,18 @@ class FastVideoArgs:
|
||||
|
||||
@classmethod
|
||||
def from_kwargs(cls, **kwargs: Any) -> "FastVideoArgs":
|
||||
# Convert mode string to enum if necessary
|
||||
if 'mode' in kwargs and isinstance(kwargs['mode'], str):
|
||||
kwargs['mode'] = ExecutionMode.from_string(kwargs['mode'])
|
||||
|
||||
# Convert workload_type string to enum if necessary
|
||||
if 'workload_type' in kwargs and isinstance(kwargs['workload_type'],
|
||||
str):
|
||||
kwargs['workload_type'] = WorkloadType.from_string(
|
||||
kwargs['workload_type'])
|
||||
|
||||
kwargs['pipeline_config'] = PipelineConfig.from_kwargs(kwargs)
|
||||
kwargs['preprocess_config'] = PreprocessConfig.from_kwargs(kwargs)
|
||||
return cls(**kwargs)
|
||||
|
||||
def check_fastvideo_args(self) -> None:
|
||||
@@ -316,6 +440,34 @@ class FastVideoArgs:
|
||||
if current_platform.is_mps():
|
||||
self.use_fsdp_inference = False
|
||||
|
||||
# Validate mode and inference_mode consistency
|
||||
assert isinstance(
|
||||
self.mode, ExecutionMode
|
||||
), f"Mode must be an ExecutionMode enum, got {type(self.mode)}"
|
||||
assert self.mode in ExecutionMode.choices(
|
||||
), f"Invalid execution mode: {self.mode}"
|
||||
|
||||
# Validate workload type
|
||||
assert isinstance(
|
||||
self.workload_type, WorkloadType
|
||||
), f"Workload type must be a WorkloadType enum, got {type(self.workload_type)}"
|
||||
assert self.workload_type in WorkloadType.choices(
|
||||
), f"Invalid workload type: {self.workload_type}"
|
||||
|
||||
if self.mode in [ExecutionMode.DISTILLATION, ExecutionMode.FINETUNING
|
||||
] and self.inference_mode:
|
||||
logger.warning(
|
||||
"Mode is 'training' but inference_mode is True. Setting inference_mode to False."
|
||||
)
|
||||
self.inference_mode = False
|
||||
elif self.mode in [
|
||||
ExecutionMode.INFERENCE, ExecutionMode.PREPROCESSING
|
||||
] and not self.inference_mode:
|
||||
logger.warning(
|
||||
"Mode is '%s' but inference_mode is False. Setting inference_mode to True.",
|
||||
self.mode)
|
||||
self.inference_mode = True
|
||||
|
||||
if not self.inference_mode:
|
||||
assert self.hsdp_replicate_dim != -1, "hsdp_replicate_dim must be set for training"
|
||||
assert self.hsdp_shard_dim != -1, "hsdp_shard_dim must be set for training"
|
||||
@@ -346,6 +498,18 @@ class FastVideoArgs:
|
||||
|
||||
self.pipeline_config.check_pipeline_config()
|
||||
|
||||
# Add preprocessing config validation if needed
|
||||
if self.mode == ExecutionMode.PREPROCESSING:
|
||||
if self.preprocess_config is None:
|
||||
raise ValueError(
|
||||
"preprocess_config is not set in FastVideoArgs when mode is PREPROCESSING"
|
||||
)
|
||||
if self.preprocess_config.model_path == "":
|
||||
self.preprocess_config.model_path = self.model_path
|
||||
if not self.pipeline_config.vae_config.load_encoder:
|
||||
self.pipeline_config.vae_config.load_encoder = True
|
||||
self.preprocess_config.check_preprocess_config()
|
||||
|
||||
|
||||
_current_fastvideo_args = None
|
||||
|
||||
@@ -503,6 +667,19 @@ class TrainingArgs(FastVideoArgs):
|
||||
if attr == 'pipeline_config':
|
||||
pipeline_config = PipelineConfig.from_kwargs(provided_args)
|
||||
kwargs[attr] = pipeline_config
|
||||
elif attr == 'mode':
|
||||
# Convert string to ExecutionMode enum
|
||||
mode_value = getattr(args, attr, ExecutionMode.FINETUNING.value)
|
||||
kwargs[attr] = ExecutionMode.from_string(
|
||||
mode_value) if isinstance(mode_value, str) else mode_value
|
||||
elif attr == 'workload_type':
|
||||
# Convert string to WorkloadType enum
|
||||
workload_type_value = getattr(args, 'workload_type',
|
||||
WorkloadType.T2V.value)
|
||||
kwargs[attr] = WorkloadType.from_string(
|
||||
workload_type_value) if isinstance(
|
||||
workload_type_value, str) else workload_type_value
|
||||
# Use getattr with default value from the dataclass for potentially missing attributes
|
||||
else:
|
||||
# Get the field to check its default value
|
||||
field = dataclasses.fields(cls)[next(
|
||||
|
||||
@@ -247,10 +247,10 @@ class TextEncoderLoader(ComponentLoader):
|
||||
target_device: torch.device,
|
||||
fastvideo_args: FastVideoArgs,
|
||||
dtype: str = "fp16"):
|
||||
use_cpu_offload = fastvideo_args.text_encoder_offload and len(
|
||||
use_cpu_offload = fastvideo_args.text_encoder_cpu_offload and len(
|
||||
getattr(model_config, "_fsdp_shard_conditions", [])) > 0
|
||||
|
||||
if fastvideo_args.text_encoder_offload:
|
||||
if fastvideo_args.text_encoder_cpu_offload:
|
||||
target_device = torch.device(
|
||||
"mps") if current_platform.is_mps() else torch.device("cpu")
|
||||
|
||||
@@ -324,7 +324,10 @@ class ImageEncoderLoader(TextEncoderLoader):
|
||||
encoder_config = fastvideo_args.pipeline_config.image_encoder_config
|
||||
encoder_config.update_model_arch(model_config)
|
||||
|
||||
target_device = get_local_torch_device()
|
||||
if fastvideo_args.image_encoder_cpu_offload:
|
||||
target_device = torch.device("mps") if current_platform.is_mps() else torch.device("cpu")
|
||||
else:
|
||||
target_device = get_local_torch_device()
|
||||
# TODO(will): add support for other dtypes
|
||||
return self.load_model(
|
||||
model_path, encoder_config, target_device, fastvideo_args,
|
||||
@@ -375,10 +378,15 @@ class VAELoader(ComponentLoader):
|
||||
vae_config = fastvideo_args.pipeline_config.vae_config
|
||||
vae_config.update_model_arch(config)
|
||||
|
||||
if fastvideo_args.vae_cpu_offload:
|
||||
target_device = torch.device("mps") if current_platform.is_mps() else torch.device("cpu")
|
||||
else:
|
||||
target_device = get_local_torch_device()
|
||||
|
||||
with set_default_torch_dtype(PRECISION_TO_TYPE[
|
||||
fastvideo_args.pipeline_config.vae_precision]):
|
||||
vae_cls, _ = ModelRegistry.resolve_model_cls(class_name)
|
||||
vae = vae_cls(vae_config).to(get_local_torch_device())
|
||||
vae = vae_cls(vae_config).to(target_device)
|
||||
|
||||
# Find all safetensors files
|
||||
safetensors_list = glob.glob(
|
||||
@@ -441,7 +449,7 @@ class TransformerLoader(ComponentLoader):
|
||||
device=get_local_torch_device(),
|
||||
hsdp_replicate_dim=fastvideo_args.hsdp_replicate_dim,
|
||||
hsdp_shard_dim=fastvideo_args.hsdp_shard_dim,
|
||||
cpu_offload=fastvideo_args.use_cpu_offload,
|
||||
cpu_offload=fastvideo_args.dit_cpu_offload,
|
||||
fsdp_inference=fastvideo_args.use_fsdp_inference,
|
||||
# TODO(will): make these configurable
|
||||
param_dtype=torch.bfloat16,
|
||||
|
||||
@@ -139,7 +139,7 @@ class ComposedPipelineBase(ABC):
|
||||
for key, value in kwargs.items():
|
||||
setattr(fastvideo_args, key, value)
|
||||
|
||||
fastvideo_args.use_cpu_offload = False
|
||||
fastvideo_args.dit_cpu_offload = False
|
||||
# we hijack the precision to be the master weight type so that the
|
||||
# model is loaded with the correct precision. Subsequently we will
|
||||
# use FSDP2's MixedPrecisionPolicy to set the precision for the
|
||||
|
||||
@@ -23,7 +23,7 @@ def main(args) -> None:
|
||||
assert num_gpus == 1, "Only support 1 GPU"
|
||||
pipeline_config = PipelineConfig.from_pretrained(args.model_path)
|
||||
kwargs = {
|
||||
"use_cpu_offload": False,
|
||||
"dit_cpu_offload": False,
|
||||
"vae_precision": "fp32",
|
||||
"vae_config": WanVAEConfig(load_encoder=True, load_decoder=False),
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
|
||||
import fastvideo.envs as envs
|
||||
from fastvideo.fastvideo_args import FastVideoArgs
|
||||
from fastvideo.logger import init_logger
|
||||
from fastvideo.pipelines.pipeline_batch_info import ForwardBatch
|
||||
@@ -145,8 +146,7 @@ class PipelineStage(ABC):
|
||||
raise
|
||||
|
||||
# Execute the actual stage logic
|
||||
# envs.ENABLE_STAGE_LOGGING
|
||||
if False:
|
||||
if envs.FASTVIDEO_STAGE_LOGGING:
|
||||
logger.info("[%s] Starting execution", stage_name)
|
||||
start_time = time.perf_counter()
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ class DecodingStage(PipelineStage):
|
||||
if hasattr(self, 'maybe_free_model_hooks'):
|
||||
self.maybe_free_model_hooks()
|
||||
|
||||
self.vae.to("cpu")
|
||||
if fastvideo_args.vae_cpu_offload:
|
||||
self.vae.to("cpu")
|
||||
|
||||
if torch.backends.mps.is_available():
|
||||
del self.vae
|
||||
|
||||
@@ -135,7 +135,8 @@ class EncodingStage(PipelineStage):
|
||||
if hasattr(self, 'maybe_free_model_hooks'):
|
||||
self.maybe_free_model_hooks()
|
||||
|
||||
self.vae.to("cpu")
|
||||
if fastvideo_args.vae_cpu_offload:
|
||||
self.vae.to("cpu")
|
||||
|
||||
return batch
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class ImageEncodingStage(PipelineStage):
|
||||
|
||||
batch.image_embeds.append(image_embeds)
|
||||
|
||||
if fastvideo_args.use_cpu_offload:
|
||||
if fastvideo_args.image_encoder_cpu_offload:
|
||||
self.image_encoder.to('cpu')
|
||||
|
||||
return batch
|
||||
|
||||
@@ -33,7 +33,7 @@ WAN_LORA_PARAMS = {
|
||||
"fps": 24,
|
||||
"neg_prompt": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
|
||||
"text-encoder-precision": ("fp32",),
|
||||
"use_cpu_offload": True,
|
||||
"dit_cpu_offload": True,
|
||||
}
|
||||
|
||||
# LoRA configurations for testing
|
||||
@@ -68,7 +68,7 @@ def test_merge_lora_weights(model_id):
|
||||
lora_path = lora_config["lora_path"]
|
||||
args = FastVideoArgs.from_kwargs(
|
||||
model_path=model_id,
|
||||
use_cpu_offload=True,
|
||||
dit_cpu_offload=True,
|
||||
dit_precision="bf16",
|
||||
)
|
||||
pipe = build_pipeline(args)
|
||||
@@ -113,7 +113,7 @@ def test_lora_inference_similarity(ATTENTION_BACKEND, model_id):
|
||||
init_kwargs = {
|
||||
"num_gpus": BASE_PARAMS["num_gpus"],
|
||||
"flow_shift": BASE_PARAMS["flow_shift"],
|
||||
"use_cpu_offload": BASE_PARAMS["use_cpu_offload"],
|
||||
"dit_cpu_offload": BASE_PARAMS["dit_cpu_offload"],
|
||||
}
|
||||
if "text-encoder-precision" in BASE_PARAMS:
|
||||
init_kwargs["text_encoder_precisions"] = BASE_PARAMS["text-encoder-precision"]
|
||||
|
||||
@@ -228,7 +228,7 @@ def test_inference_similarity(prompt, ATTENTION_BACKEND, model_id):
|
||||
"flow_shift": BASE_PARAMS["flow_shift"],
|
||||
"sp_size": BASE_PARAMS["sp_size"],
|
||||
"tp_size": BASE_PARAMS["tp_size"],
|
||||
"use_cpu_offload": True,
|
||||
"dit_cpu_offload": True,
|
||||
}
|
||||
if BASE_PARAMS.get("vae_sp"):
|
||||
init_kwargs["vae_sp"] = True
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_hunyuanvideo_distributed():
|
||||
|
||||
precision_str = "bf16"
|
||||
args = FastVideoArgs(model_path=TRANSFORMER_PATH,
|
||||
use_cpu_offload=True,
|
||||
dit_cpu_offload=True,
|
||||
pipeline_config=PipelineConfig(dit_config=HunyuanVideoConfig(), dit_precision=precision_str))
|
||||
args.device = torch.device(f"cuda:{LOCAL_RANK}")
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_wan_transformer():
|
||||
precision = torch.bfloat16
|
||||
precision_str = "bf16"
|
||||
args = FastVideoArgs(model_path=TRANSFORMER_PATH,
|
||||
use_cpu_offload=True,
|
||||
dit_cpu_offload=True,
|
||||
pipeline_config=PipelineConfig(dit_config=WanVideoConfig(), dit_precision=precision_str))
|
||||
args.device = device
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ def test_hunyuan_vae():
|
||||
precision_str = "bf16"
|
||||
args = FastVideoArgs(model_path=VAE_PATH, pipeline_config=PipelineConfig(vae_config=HunyuanVAEConfig(), vae_precision=precision_str))
|
||||
args.device = device
|
||||
args.vae_cpu_offload = False
|
||||
|
||||
loader = VAELoader()
|
||||
model = loader.load(VAE_PATH, args)
|
||||
|
||||
@@ -32,6 +32,7 @@ def test_wan_vae():
|
||||
precision_str = "bf16"
|
||||
args = FastVideoArgs(model_path=VAE_PATH, pipeline_config=PipelineConfig(vae_config=WanVAEConfig(), vae_precision=precision_str))
|
||||
args.device = device
|
||||
args.vae_cpu_offload = False
|
||||
|
||||
loader = VAELoader()
|
||||
model2 = loader.load(VAE_PATH, args)
|
||||
|
||||
@@ -619,7 +619,7 @@ class TrainingPipeline(LoRAPipeline, ABC):
|
||||
"""
|
||||
assert training_args is not None
|
||||
training_args.inference_mode = True
|
||||
training_args.use_cpu_offload = True
|
||||
training_args.dit_cpu_offload = True
|
||||
if not training_args.log_validation:
|
||||
return
|
||||
if self.validation_pipeline is None:
|
||||
|
||||
@@ -46,7 +46,7 @@ class WanI2VTrainingPipeline(TrainingPipeline):
|
||||
args_copy = deepcopy(training_args)
|
||||
|
||||
args_copy.inference_mode = True
|
||||
args_copy.use_cpu_offload = True
|
||||
args_copy.dit_cpu_offload = True
|
||||
# args_copy.pipeline_config.vae_config.load_encoder = False
|
||||
# validation_pipeline = WanImageToVideoValidationPipeline.from_pretrained(
|
||||
self.validation_pipeline = WanImageToVideoPipeline.from_pretrained(
|
||||
@@ -59,7 +59,7 @@ class WanI2VTrainingPipeline(TrainingPipeline):
|
||||
tp_size=training_args.tp_size,
|
||||
sp_size=training_args.sp_size,
|
||||
num_gpus=training_args.num_gpus,
|
||||
use_cpu_offload=True)
|
||||
dit_cpu_offload=True)
|
||||
|
||||
def _get_next_batch(self, training_batch: TrainingBatch) -> TrainingBatch:
|
||||
assert self.training_args is not None
|
||||
@@ -225,5 +225,5 @@ if __name__ == "__main__":
|
||||
parser = TrainingArgs.add_cli_args(parser)
|
||||
parser = FastVideoArgs.add_cli_args(parser)
|
||||
args = parser.parse_args()
|
||||
args.use_cpu_offload = False
|
||||
args.dit_cpu_offload = False
|
||||
main(args)
|
||||
|
||||
@@ -36,7 +36,7 @@ class WanTrainingPipeline(TrainingPipeline):
|
||||
args_copy = deepcopy(training_args)
|
||||
|
||||
args_copy.inference_mode = True
|
||||
args_copy.use_cpu_offload = True
|
||||
args_copy.dit_cpu_offload = True
|
||||
args_copy.pipeline_config.vae_config.load_encoder = False
|
||||
validation_pipeline = WanPipeline.from_pretrained(
|
||||
training_args.model_path,
|
||||
@@ -70,5 +70,5 @@ if __name__ == "__main__":
|
||||
parser = TrainingArgs.add_cli_args(parser)
|
||||
parser = FastVideoArgs.add_cli_args(parser)
|
||||
args = parser.parse_args()
|
||||
args.use_cpu_offload = False
|
||||
args.dit_cpu_offload = False
|
||||
main(args)
|
||||
|
||||
@@ -139,3 +139,13 @@ column_limit = 80
|
||||
line_length = 80
|
||||
use_parentheses = true
|
||||
skip_gitignore = true
|
||||
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/hao-ai-lab/FastVideo"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "fastvideo"
|
||||
DisplayName = "FastVideo"
|
||||
Icon = "https://raw.githubusercontent.com/hao-ai-lab/FastVideo/main/comfyui/assets/logo.png"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
num_gpus=2
|
||||
num_gpus=1
|
||||
export FASTVIDEO_ATTENTION_BACKEND=
|
||||
export MODEL_BASE=Wan-AI/Wan2.1-T2V-1.3B-Diffusers
|
||||
# export MODEL_BASE=hunyuanvideo-community/HunyuanVideo
|
||||
# You can either use --prompt or --prompt-txt, but not both.
|
||||
fastvideo generate \
|
||||
--model-path $MODEL_BASE \
|
||||
--sp-size $num_gpus \
|
||||
@@ -14,8 +15,9 @@ fastvideo generate \
|
||||
--num-frames 77 \
|
||||
--num-inference-steps 50 \
|
||||
--fps 16 \
|
||||
--guidance-scale 3.0 \
|
||||
--prompt "A beautiful woman in a red dress walking down a street" \
|
||||
--guidance-scale 6.0 \
|
||||
--flow-shift 8.0 \
|
||||
--prompt-txt assets/prompt.txt \
|
||||
--negative-prompt "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" \
|
||||
--seed 1024 \
|
||||
--output-path outputs_video/
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
num_gpus=1
|
||||
export FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN
|
||||
export MODEL_BASE=FastVideo/FastWan2.1-T2V-1.3B-Diffusers
|
||||
# export MODEL_BASE=hunyuanvideo-community/HunyuanVideo
|
||||
# You can either use --prompt or --prompt-txt, but not both.
|
||||
fastvideo generate \
|
||||
--model-path $MODEL_BASE \
|
||||
--sp-size $num_gpus \
|
||||
--tp-size 1 \
|
||||
--num-gpus $num_gpus \
|
||||
--height 448 \
|
||||
--width 832 \
|
||||
--num-frames 61 \
|
||||
--num-inference-steps 3 \
|
||||
--fps 16 \
|
||||
--prompt-txt assets/prompt.txt \
|
||||
--negative-prompt "Bright tones, overexposed, static, blurred details, subtitles, style, works, paintings, images, static, overall gray, worst quality, low quality, JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, still picture, messy background, three legs, many people in the background, walking backwards" \
|
||||
--seed 1024 \
|
||||
--output-path outputs_video_dmd/ \
|
||||
--VSA-sparsity 0.8 \
|
||||
--dmd-denoising-steps "1000,757,522"
|
||||
Reference in New Issue
Block a user