Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb46d63f07 | ||
|
|
bbbb7ab021 |
@@ -330,7 +330,7 @@ at FastVideo's CI — before the Dynamo-side integration even knows.
|
||||
internal; presets identify them by name on
|
||||
`PipelineSelection.preset`).
|
||||
* `fastvideo.fastvideo_args.FastVideoArgs` (legacy compat type).
|
||||
* `fastvideo.api.compat.*` private helpers
|
||||
* `fastvideo.api.translation.*` private helpers
|
||||
(`_validate_continuation_state` etc.) — the public boundary is
|
||||
`VideoGenerator` + `fastvideo.api`.
|
||||
* Any flat legacy LTX-2 kwarg (`ltx2_refine_upsampler_path`,
|
||||
|
||||
@@ -48,15 +48,17 @@ highest first:
|
||||
`request.model_fields_set` (Pydantic v2). Unset fields do not count,
|
||||
even if the Pydantic model has a schema default for them.
|
||||
2. **`ServeConfig.default_request` (operator-explicit)** — projected via
|
||||
[`explicit_request_updates()`](https://github.com/hao-ai-lab/FastVideo/blob/main/fastvideo/api/compat.py);
|
||||
[`explicit_request_updates()`](https://github.com/hao-ai-lab/FastVideo/blob/main/fastvideo/api/translation.py);
|
||||
only fields the operator actually wrote into the YAML count as
|
||||
defaults. Every other field inherits the schema default rather than
|
||||
being pinned.
|
||||
defaults (an explicit `null` counts as unset). Every other sampling
|
||||
field stays `None` — "inherit the model preset" — and other sections
|
||||
keep their schema defaults without being pinned.
|
||||
3. **Hardcoded fallback** — e.g. `fps = 24`.
|
||||
|
||||
The gate matters: both surfaces carry schema defaults. Without
|
||||
`model_fields_set` / explicit-path tracking, schema defaults would
|
||||
masquerade as intent and silently shadow the other side.
|
||||
The gate matters: the Pydantic surface carries schema defaults and the
|
||||
dataclass surface carries non-None defaults outside `sampling`. Without
|
||||
`model_fields_set` / explicit-path tracking, defaults would masquerade
|
||||
as intent and silently shadow the other side.
|
||||
|
||||
See [`video_api.py::_build_generation_kwargs`](https://github.com/hao-ai-lab/FastVideo/blob/main/fastvideo/entrypoints/openai/video_api.py)
|
||||
for the canonical implementation; the per-request assembly lives there,
|
||||
|
||||
@@ -39,17 +39,20 @@ All you need to generate videos using multi-gpus from state-of-the-art diffusion
|
||||
|
||||
```python
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import EngineConfig, GenerationRequest, GeneratorConfig
|
||||
|
||||
def main():
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
num_gpus=1,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
)
|
||||
)
|
||||
|
||||
prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones.")
|
||||
video = generator.generate_video(prompt)
|
||||
result = generator.generate(GenerationRequest(prompt=prompt))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples"
|
||||
def main():
|
||||
@@ -8,29 +9,32 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
# sampling_param.num_frames = 45
|
||||
# sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
|
||||
# Generate videos with the same simple API, regardless of GPU count
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True)
|
||||
# video = generator.generate_video(prompt, sampling_param=sampling_param, output_path="wan_t2v_videos/")
|
||||
video = generator.generate(
|
||||
GenerationRequest(prompt=prompt, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True)))
|
||||
|
||||
# Generate another video with a different prompt, without reloading the
|
||||
# model!
|
||||
@@ -40,7 +44,8 @@ def main():
|
||||
"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.")
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(prompt=prompt2, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Point this to your local diffusers model dir (or replace with a HF model ID).
|
||||
model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers"
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
sampling_param = SamplingParam.from_pretrained(model_path)
|
||||
|
||||
# image2world example from official repo
|
||||
image_path = "assets/images/bus_terminal.jpg"
|
||||
|
||||
@@ -33,13 +39,16 @@ def main():
|
||||
"Overhead signage in Chinese characters remains illuminated, enhancing the vibrant, urban night scene."
|
||||
)
|
||||
|
||||
generator.generate_video(
|
||||
prompt,
|
||||
sampling_param=sampling_param,
|
||||
image_path=str(image_path),
|
||||
num_cond_frames=1,
|
||||
output_path="outputs_video/cosmos2_5_i2w.mp4",
|
||||
save_video=True,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=str(image_path)),
|
||||
output=OutputConfig(
|
||||
output_path="outputs_video/cosmos2_5_i2w.mp4",
|
||||
save_video=True,
|
||||
),
|
||||
extensions={"num_cond_frames": 1},
|
||||
)
|
||||
)
|
||||
|
||||
generator.shutdown()
|
||||
@@ -47,4 +56,3 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Point this to your local diffusers model dir (or replace with a HF model ID).
|
||||
model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers"
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Load default sampling parameters (negative_prompt, resolution, steps, etc.)
|
||||
sampling_param = SamplingParam.from_pretrained(model_path)
|
||||
|
||||
prompt = (
|
||||
"A high-definition video captures the precision of robotic welding in an industrial setting. "
|
||||
"The first frame showcases a robotic arm, equipped with a welding torch, positioned over a large metal structure. "
|
||||
@@ -34,11 +39,14 @@ def main():
|
||||
"underscoring the ongoing nature of the welding operation."
|
||||
)
|
||||
|
||||
generator.generate_video(
|
||||
prompt,
|
||||
sampling_param=sampling_param,
|
||||
output_path="outputs_video/cosmos2_5_t2w.mp4",
|
||||
save_video=True,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
output=OutputConfig(
|
||||
output_path="outputs_video/cosmos2_5_t2w.mp4",
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
generator.shutdown()
|
||||
@@ -46,6 +54,3 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig,
|
||||
OffloadConfig, OutputConfig,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Point this to your local diffusers model dir (or replace with a HF model ID).
|
||||
model_path = "KyleShao/Cosmos-Predict2.5-2B-Diffusers"
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
)
|
||||
|
||||
sampling_param = SamplingParam.from_pretrained(model_path)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
# video2world example from official repo
|
||||
video_path = "assets/videos/robot_pouring.mp4"
|
||||
@@ -36,18 +42,19 @@ def main():
|
||||
"The final frame captures the robotic arm with the pitcher finishing the pour, with the glass now filled to a higher level, while the pitcher is slightly tilted but still held securely by the gripper."
|
||||
)
|
||||
|
||||
generator.generate_video(
|
||||
prompt,
|
||||
sampling_param=sampling_param,
|
||||
video_path=str(video_path),
|
||||
num_cond_frames=1,
|
||||
output_path="outputs_video/cosmos2_5_v2w.mp4",
|
||||
save_video=True,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(video_path=str(video_path)),
|
||||
output=OutputConfig(
|
||||
output_path="outputs_video/cosmos2_5_v2w.mp4",
|
||||
save_video=True,
|
||||
),
|
||||
extensions={"num_cond_frames": 1},
|
||||
))
|
||||
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import os
|
||||
import time
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, PipelineSelection,
|
||||
SamplingConfig)
|
||||
|
||||
OUTPUT_PATH = "video_samples_dmd2"
|
||||
def main():
|
||||
@@ -10,30 +12,36 @@ def main():
|
||||
|
||||
load_start_time = time.perf_counter()
|
||||
model_name = "FastVideo/FastWan2.1-T2V-1.3B-Diffusers"
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_name,
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
# Adjust these offload parameters if you have < 32GB of VRAM
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
VSA_sparsity=0.8,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_name,
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
# Adjust these offload parameters if you have < 32GB of VRAM
|
||||
offload=OffloadConfig(
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
dit=False,
|
||||
vae=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(experimental={"VSA_sparsity": 0.8}),
|
||||
))
|
||||
load_end_time = time.perf_counter()
|
||||
load_time = load_end_time - load_start_time
|
||||
|
||||
|
||||
sampling_param = SamplingParam.from_pretrained(model_name)
|
||||
sampling_param.num_frames = 81
|
||||
|
||||
prompt = (
|
||||
"A neon-lit alley in futuristic Tokyo during a heavy rainstorm at night. The puddles reflect glowing signs in kanji, advertising ramen, karaoke, and VR arcades. A woman in a translucent raincoat walks briskly with an LED umbrella. Steam rises from a street food cart, and a cat darts across the screen. Raindrops are visible on the camera lens, creating a cinematic bokeh effect."
|
||||
)
|
||||
start_time = time.perf_counter()
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
end_time = time.perf_counter()
|
||||
gen_time = end_time - start_time
|
||||
|
||||
@@ -46,7 +54,12 @@ def main():
|
||||
"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, num_frames=81)
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
sampling=SamplingConfig(num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
end_time = time.perf_counter()
|
||||
gen_time2 = end_time - start_time
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import os
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest,
|
||||
GeneratorConfig, InputConfig, OffloadConfig,
|
||||
OutputConfig, PipelineSelection, SamplingConfig)
|
||||
|
||||
|
||||
OUTPUT_PATH = os.getenv("DREAMX_WORLD_OUTPUT_PATH", "video_samples_dreamx_world")
|
||||
@@ -16,16 +19,22 @@ def _env_float(name: str, default: float) -> float:
|
||||
|
||||
def main():
|
||||
model_name = os.getenv("DREAMX_WORLD_MODEL_DIR", "FastVideo/DreamX-World-5B-Cam-Diffusers")
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_name,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
override_pipeline_cls_name="DreamXWorldPipeline",
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_name,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(override_pipeline_cls_name="DreamXWorldPipeline"), ),
|
||||
))
|
||||
|
||||
prompt = os.getenv(
|
||||
"DREAMX_WORLD_PROMPT",
|
||||
@@ -37,25 +46,31 @@ def main():
|
||||
"https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG",
|
||||
)
|
||||
|
||||
kwargs = {
|
||||
"output_path": OUTPUT_PATH,
|
||||
"save_video": os.getenv("DREAMX_WORLD_SAVE_VIDEO", "1") != "0",
|
||||
"height": _env_int("DREAMX_WORLD_HEIGHT", 480),
|
||||
"width": _env_int("DREAMX_WORLD_WIDTH", 832),
|
||||
"num_frames": _env_int("DREAMX_WORLD_NUM_FRAMES", 161),
|
||||
"num_inference_steps": _env_int("DREAMX_WORLD_STEPS", 30),
|
||||
"guidance_scale": _env_float("DREAMX_WORLD_GUIDANCE", 5.0),
|
||||
"action_list": os.getenv("DREAMX_WORLD_ACTIONS", "w,d,w").split(","),
|
||||
"action_speed_list": [
|
||||
float(value)
|
||||
for value in os.getenv("DREAMX_WORLD_ACTION_SPEEDS", "4.0,2.0,4.0").split(",")
|
||||
],
|
||||
}
|
||||
if image_path:
|
||||
kwargs["image_path"] = image_path
|
||||
request = GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path or None),
|
||||
sampling=SamplingConfig(
|
||||
height=_env_int("DREAMX_WORLD_HEIGHT", 480),
|
||||
width=_env_int("DREAMX_WORLD_WIDTH", 832),
|
||||
num_frames=_env_int("DREAMX_WORLD_NUM_FRAMES", 161),
|
||||
num_inference_steps=_env_int("DREAMX_WORLD_STEPS", 30),
|
||||
guidance_scale=_env_float("DREAMX_WORLD_GUIDANCE", 5.0),
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=os.getenv("DREAMX_WORLD_SAVE_VIDEO", "1") != "0",
|
||||
),
|
||||
extensions={
|
||||
"action_list": os.getenv("DREAMX_WORLD_ACTIONS", "w,d,w").split(","),
|
||||
"action_speed_list": [
|
||||
float(value)
|
||||
for value in os.getenv("DREAMX_WORLD_ACTION_SPEEDS", "4.0,2.0,4.0").split(",")
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
generator.generate_video(prompt, **kwargs)
|
||||
generator.generate(request)
|
||||
finally:
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
@@ -26,6 +26,15 @@ import os
|
||||
import torch
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
InputConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.models.camera import create_camera_trajectory
|
||||
|
||||
# Model configuration (use GAMECRAFT_MODEL_PATH for local weights)
|
||||
@@ -55,14 +64,20 @@ OUTPUT_PATH = "video_samples_gamecraft"
|
||||
def main():
|
||||
# Initialize generator
|
||||
# FastVideo will automatically download weights from HuggingFace
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
MODEL_PATH,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=MODEL_PATH,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Video parameters
|
||||
@@ -96,23 +111,27 @@ def main():
|
||||
prompt = DEFAULT_I2V_PROMPT if is_i2v else DEFAULT_PROMPTS["temple"]
|
||||
print(f"Mode: {'I2V' if is_i2v else 'T2V'}, prompt: {prompt[:60]}...")
|
||||
|
||||
gen_kw = dict(
|
||||
request = GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt="",
|
||||
camera_states=camera_states,
|
||||
height=height,
|
||||
width=width,
|
||||
num_frames=num_frames,
|
||||
num_inference_steps=50,
|
||||
guidance_scale=6.0,
|
||||
seed=42,
|
||||
fps=24,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
sampling=SamplingConfig(
|
||||
height=height,
|
||||
width=width,
|
||||
num_frames=num_frames,
|
||||
num_inference_steps=50,
|
||||
guidance_scale=6.0,
|
||||
seed=42,
|
||||
fps=24,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
extensions={"camera_states": camera_states},
|
||||
)
|
||||
if is_i2v:
|
||||
gen_kw["image_path"] = image_path
|
||||
generator.generate_video(**gen_kw)
|
||||
request.inputs = InputConfig(image_path=image_path)
|
||||
generator.generate(request)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -22,6 +22,10 @@ Requirements:
|
||||
import argparse
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig,
|
||||
OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
@@ -74,33 +78,47 @@ def main():
|
||||
parser.add_argument("--seed", type=int, default=42)
|
||||
args = parser.parse_args()
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
args.model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=args.model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
video = generator.generate_video(
|
||||
args.prompt,
|
||||
negative_prompt=args.negative_prompt,
|
||||
image_path=args.image_path,
|
||||
trajectory_type=args.trajectory,
|
||||
movement_distance=args.movement_distance,
|
||||
camera_rotation=args.camera_rotation,
|
||||
height=args.height,
|
||||
width=args.width,
|
||||
num_frames=args.num_frames,
|
||||
num_inference_steps=args.num_inference_steps,
|
||||
guidance_scale=args.guidance_scale,
|
||||
fps=24,
|
||||
seed=args.seed,
|
||||
output_path=args.output_path,
|
||||
save_video=True,
|
||||
)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=args.prompt,
|
||||
negative_prompt=args.negative_prompt,
|
||||
inputs=InputConfig(
|
||||
image_path=args.image_path,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
height=args.height,
|
||||
width=args.width,
|
||||
num_frames=args.num_frames,
|
||||
num_inference_steps=args.num_inference_steps,
|
||||
guidance_scale=args.guidance_scale,
|
||||
fps=24,
|
||||
seed=args.seed,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=args.output_path,
|
||||
save_video=True,
|
||||
),
|
||||
extensions={
|
||||
"trajectory_type": args.trajectory,
|
||||
"movement_distance": args.movement_distance,
|
||||
"camera_rotation": args.camera_rotation,
|
||||
},
|
||||
))
|
||||
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
import json
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
|
||||
OUTPUT_PATH = "video_samples_hy15"
|
||||
def main():
|
||||
@@ -8,17 +15,21 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
|
||||
generator = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path="hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder_cpu_offload=False,
|
||||
)
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
@@ -26,7 +37,12 @@ def main():
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, negative_prompt="", num_frames=81, fps=16)
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt="",
|
||||
sampling=SamplingConfig(num_frames=81, fps=16),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
prompt2 = (
|
||||
"A majestic lion strides across the golden savanna, its powerful frame "
|
||||
@@ -35,8 +51,13 @@ def main():
|
||||
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
|
||||
"cinematic.")
|
||||
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, negative_prompt="", num_frames=81, fps=16)
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt2,
|
||||
negative_prompt="",
|
||||
sampling=SamplingConfig(num_frames=81, fps=16),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
)
|
||||
import json
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
|
||||
OUTPUT_PATH = "video_samples_hy15_1080p"
|
||||
def main():
|
||||
@@ -8,17 +14,23 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR", # 480p -> 720p -> 1080p
|
||||
# or "weizhou03/HunyuanVideo-1.5-Diffusers-1080p" # 720p -> 1080p
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="weizhou03/HunyuanVideo-1.5-Diffusers-1080p-2SR", # 480p -> 720p -> 1080p
|
||||
# or "weizhou03/HunyuanVideo-1.5-Diffusers-1080p" # 720p -> 1080p
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
prompt = (
|
||||
@@ -27,7 +39,13 @@ def main():
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, negative_prompt="")
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt="",
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
prompt2 = (
|
||||
"A majestic lion strides across the golden savanna, its powerful frame "
|
||||
@@ -36,7 +54,13 @@ def main():
|
||||
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
|
||||
"cinematic.")
|
||||
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, negative_prompt="")
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
negative_prompt="",
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig,
|
||||
SamplingConfig)
|
||||
from fastvideo.models.dits.hyworld.resolution_utils import get_resolution_from_image
|
||||
|
||||
# Default prompt from HY-WorldPlay run.sh
|
||||
@@ -31,33 +33,45 @@ def main():
|
||||
|
||||
# Initialize generator
|
||||
print("\nInitializing VideoGenerator for HYWorld...")
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/HY-WorldPlay-Bidirectional-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
image_encoder_cpu_offload=True,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/HY-WorldPlay-Bidirectional-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
image_encoder=True,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
# Generate video
|
||||
# The pose string is automatically converted to camera matrices by the pipeline
|
||||
print("\nGenerating video...")
|
||||
generator.generate_video(
|
||||
prompt=args.prompt,
|
||||
image_path=args.image,
|
||||
pose=args.pose, # Camera trajectory control
|
||||
output_path=args.output_path,
|
||||
save_video=True,
|
||||
negative_prompt="",
|
||||
num_frames=args.num_frames,
|
||||
fps=24,
|
||||
height=HEIGHT,
|
||||
width=WIDTH,
|
||||
seed=args.seed,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=args.prompt,
|
||||
negative_prompt="",
|
||||
inputs=InputConfig(
|
||||
image_path=args.image,
|
||||
pose=args.pose, # Camera trajectory control
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
num_frames=args.num_frames,
|
||||
fps=24,
|
||||
height=HEIGHT,
|
||||
width=WIDTH,
|
||||
seed=args.seed,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=args.output_path,
|
||||
save_video=True,
|
||||
),
|
||||
))
|
||||
|
||||
print(f"\nVideo saved to: {args.output_path}")
|
||||
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
InputConfig, OffloadConfig, OutputConfig,
|
||||
SamplingConfig)
|
||||
|
||||
OUTPUT_PATH = "video_samples_kandinsky5_i2v"
|
||||
|
||||
IMAGE_PATH = "assets/girl.png"
|
||||
|
||||
|
||||
def main():
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers",
|
||||
# "kandinskylab/Kandinsky-5.0-I2V-Pro-sft-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-I2V-Lite-5s-Diffusers"
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="kandinskylab/Kandinsky-5.0-I2V-Pro-distilled-5s-Diffusers",
|
||||
# "kandinskylab/Kandinsky-5.0-I2V-Pro-sft-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-I2V-Lite-5s-Diffusers"
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A woman stands up and walks away"
|
||||
)
|
||||
_ = generator.generate_video(
|
||||
prompt,
|
||||
image_path=IMAGE_PATH,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
height=1024,
|
||||
width=1024,
|
||||
num_frames=121,
|
||||
)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=IMAGE_PATH),
|
||||
sampling=SamplingConfig(height=1024, width=1024, num_frames=121),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,28 +1,41 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, SamplingConfig)
|
||||
|
||||
OUTPUT_PATH = "video_samples_kandinsky5_t2v"
|
||||
|
||||
def main():
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers",
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Pro-sft-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Lite-distilled16steps-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Pro-distilled-5s-Diffusers"
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="kandinskylab/Kandinsky-5.0-T2V-Lite-sft-5s-Diffusers",
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Pro-sft-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Lite-distilled16steps-5s-Diffusers"
|
||||
# "kandinskylab/Kandinsky-5.0-T2V-Pro-distilled-5s-Diffusers"
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
_ = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True,height=512, width=768, num_frames=121)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(height=512, width=768, num_frames=121),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
prompt2 = (
|
||||
"A majestic lion strides across the golden savanna, its powerful frame "
|
||||
@@ -30,8 +43,13 @@ def main():
|
||||
"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.")
|
||||
_ = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, height=512, width=768, num_frames=121)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
sampling=SamplingConfig(height=512, width=768, num_frames=121),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
from fastvideo.models.dits.lingbotworld.cam_utils import prepare_camera_embedding
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
OUTPUT_PATH = "video_samples_lingbotworld"
|
||||
def main():
|
||||
# FastVideo will automatically use the optimal default arguments for the
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LingBot-World-Base-Cam-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LingBot-World-Base-Cam-Diffusers",
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
num_frames = 81
|
||||
@@ -33,15 +40,23 @@ def main():
|
||||
spatial_scale=8,
|
||||
)
|
||||
|
||||
generator.generate_video(
|
||||
prompt,
|
||||
image_path=image_path,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
num_frames=num_frames,
|
||||
height=480,
|
||||
width=832,
|
||||
c2ws_plucker_emb=c2ws_plucker_emb,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(
|
||||
image_path=image_path,
|
||||
c2ws_plucker_emb=c2ws_plucker_emb,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
num_frames=num_frames,
|
||||
height=480,
|
||||
width=832,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ import glob
|
||||
import os
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
InputConfig, OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
# Common prompts and settings matching the shell script examples
|
||||
PROMPT = (
|
||||
@@ -45,41 +49,50 @@ SEED = 42
|
||||
def basic_generation():
|
||||
"""
|
||||
Run basic LongCat I2V generation (50 steps at 480p).
|
||||
|
||||
|
||||
This uses the full 50-step denoising process for highest quality.
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("LongCat I2V: Basic Generation (50 steps, 480p)")
|
||||
print("=" * 60)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-I2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-I2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(experimental={"enable_bsa": False}),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
output_path = "outputs_video/longcat_i2v_basic"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
image_path=IMAGE_PATH,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=480, # Square
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(image_path=IMAGE_PATH),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=480, # Square
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(output_path=output_path, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"\nBasic generation complete! Video saved to: {output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
@@ -87,55 +100,70 @@ def basic_generation():
|
||||
def distill_refine_generation():
|
||||
"""
|
||||
Run LongCat I2V with distill+refine pipeline (16 steps + refinement to 768p).
|
||||
|
||||
|
||||
This uses the distilled LoRA for fast 480p generation (16 steps),
|
||||
then refines to 768p using the refinement LoRA with BSA enabled.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("LongCat I2V: Distill + Refine Pipeline")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
# Stage 1: Distilled generation (16 steps at 480p)
|
||||
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
|
||||
print("-" * 40)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-I2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
lora_nickname="distilled",
|
||||
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-I2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": False,
|
||||
"lora_nickname": "distilled",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
distill_output_path = "outputs_video/longcat_i2v_distill"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
image_path=IMAGE_PATH,
|
||||
output_path=distill_output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=480, # Square
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(image_path=IMAGE_PATH),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=480, # Square
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(output_path=distill_output_path, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
# Stage 2: Refinement (480p -> 768p)
|
||||
print("\n[Stage 2] Refinement (480p -> 768p with BSA)")
|
||||
print("-" * 40)
|
||||
|
||||
|
||||
# Find the actual saved video file from stage 1
|
||||
video_files = glob.glob(os.path.join(distill_output_path, "*.mp4"))
|
||||
if not video_files:
|
||||
@@ -143,46 +171,63 @@ def distill_refine_generation():
|
||||
# Use the most recently created video file
|
||||
distill_video_path = max(video_files, key=os.path.getmtime)
|
||||
print(f"Using stage 1 video: {distill_video_path}")
|
||||
|
||||
|
||||
# Create a new generator with refinement LoRA and BSA enabled
|
||||
# Note: Refinement uses the T2V model (not I2V) since it's upscaling the generated video
|
||||
# For BSA [4, 4, 8]: latent must be divisible by 8
|
||||
# 768x768: latent 48x48, 48%8=0 ✓
|
||||
refine_generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=True,
|
||||
bsa_sparsity=0.875,
|
||||
bsa_chunk_q=[4, 4, 4],
|
||||
bsa_chunk_k=[4, 4, 4],
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
lora_nickname="refinement",
|
||||
refine_generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": True,
|
||||
"bsa_sparsity": 0.875,
|
||||
"bsa_chunk_q": [4, 4, 4],
|
||||
"bsa_chunk_k": [4, 4, 4],
|
||||
"lora_nickname": "refinement",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
refine_output_path = "outputs_video/longcat_i2v_refine_720p"
|
||||
|
||||
refine_generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output_path=refine_output_path,
|
||||
save_video=True,
|
||||
refine_from=distill_video_path,
|
||||
t_thresh=0.5,
|
||||
spatial_refine_only=False,
|
||||
num_cond_frames=0,
|
||||
height=720,
|
||||
width=720,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
refine_generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(refine_from=distill_video_path),
|
||||
sampling=SamplingConfig(
|
||||
height=720,
|
||||
width=720,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(output_path=refine_output_path, save_video=True),
|
||||
extensions={
|
||||
"t_thresh": 0.5,
|
||||
"spatial_refine_only": False,
|
||||
"num_cond_frames": 0,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Refinement complete! Video saved to: {refine_output_path}")
|
||||
refine_generator.shutdown()
|
||||
|
||||
@@ -192,13 +237,13 @@ def main():
|
||||
print("\n" + "=" * 60)
|
||||
print("LongCat Image-to-Video Example")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
|
||||
# Run basic generation
|
||||
basic_generation()
|
||||
|
||||
|
||||
# Run distill+refine pipeline
|
||||
distill_refine_generation()
|
||||
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All generations complete!")
|
||||
print("=" * 60)
|
||||
|
||||
@@ -13,6 +13,10 @@ import glob
|
||||
import os
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
InputConfig, OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
# Common prompts and settings matching the shell script examples
|
||||
PROMPT = (
|
||||
@@ -38,40 +42,54 @@ SEED = 42
|
||||
def basic_generation():
|
||||
"""
|
||||
Run basic LongCat T2V generation (50 steps at 480p).
|
||||
|
||||
|
||||
This uses the full 50-step denoising process for highest quality.
|
||||
"""
|
||||
print("=" * 60)
|
||||
print("LongCat T2V: Basic Generation (50 steps, 480p)")
|
||||
print("=" * 60)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
experimental={"enable_bsa": False},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
output_path = "outputs_video/longcat_t2v_basic"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"\nBasic generation complete! Video saved to: {output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
@@ -79,54 +97,72 @@ def basic_generation():
|
||||
def distill_refine_generation():
|
||||
"""
|
||||
Run LongCat T2V with distill+refine pipeline (16 steps + refinement to 720p).
|
||||
|
||||
|
||||
This uses the distilled LoRA for fast 480p generation (16 steps),
|
||||
then refines to 720p using the refinement LoRA with BSA enabled.
|
||||
"""
|
||||
print("\n" + "=" * 60)
|
||||
print("LongCat T2V: Distill + Refine Pipeline")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
# Stage 1: Distilled generation (16 steps at 480p)
|
||||
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
|
||||
print("-" * 40)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
lora_nickname="distilled",
|
||||
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": False,
|
||||
"lora_nickname": "distilled",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
distill_output_path = "outputs_video/longcat_t2v_distill"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output_path=distill_output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path=distill_output_path,
|
||||
save_video=True,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
# Stage 2: Refinement (480p -> 720p)
|
||||
print("\n[Stage 2] Refinement (480p -> 720p with BSA)")
|
||||
print("-" * 40)
|
||||
|
||||
|
||||
# Find the actual saved video file from stage 1
|
||||
video_files = glob.glob(os.path.join(distill_output_path, "*.mp4"))
|
||||
if not video_files:
|
||||
@@ -134,43 +170,65 @@ def distill_refine_generation():
|
||||
# Use the most recently created video file
|
||||
distill_video_path = max(video_files, key=os.path.getmtime)
|
||||
print(f"Using stage 1 video: {distill_video_path}")
|
||||
|
||||
|
||||
# Create a new generator with refinement LoRA and BSA enabled
|
||||
refine_generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=True,
|
||||
bsa_sparsity=0.875,
|
||||
bsa_chunk_q=[4, 4, 8],
|
||||
bsa_chunk_k=[4, 4, 8],
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
lora_nickname="refinement",
|
||||
refine_generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": True,
|
||||
"bsa_sparsity": 0.875,
|
||||
"bsa_chunk_q": [4, 4, 8],
|
||||
"bsa_chunk_k": [4, 4, 8],
|
||||
"lora_nickname": "refinement",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
refine_output_path = "outputs_video/longcat_t2v_refine_720p"
|
||||
|
||||
refine_generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output_path=refine_output_path,
|
||||
save_video=True,
|
||||
refine_from=distill_video_path,
|
||||
t_thresh=0.5,
|
||||
spatial_refine_only=False,
|
||||
num_cond_frames=0,
|
||||
height=720,
|
||||
width=1280,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
refine_generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path=refine_output_path,
|
||||
save_video=True,
|
||||
),
|
||||
inputs=InputConfig(
|
||||
refine_from=distill_video_path,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
height=720,
|
||||
width=1280,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
extensions={
|
||||
"t_thresh": 0.5,
|
||||
"spatial_refine_only": False,
|
||||
"num_cond_frames": 0,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Refinement complete! Video saved to: {refine_output_path}")
|
||||
refine_generator.shutdown()
|
||||
|
||||
@@ -180,13 +238,13 @@ def main():
|
||||
print("\n" + "=" * 60)
|
||||
print("LongCat Text-to-Video Example")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
|
||||
# Run basic generation
|
||||
basic_generation()
|
||||
|
||||
|
||||
# Run distill+refine pipeline
|
||||
distill_refine_generation()
|
||||
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("All generations complete!")
|
||||
print("=" * 60)
|
||||
@@ -194,5 +252,3 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ import glob
|
||||
import os
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig,
|
||||
PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
# Common prompts and settings matching the shell script examples
|
||||
PROMPT = (
|
||||
@@ -63,35 +67,49 @@ def basic_generation():
|
||||
"Please provide a valid video path."
|
||||
)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-VC-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-VC-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
experimental={"enable_bsa": False},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
output_path = "outputs_video/longcat_vc_basic"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
video_path=VIDEO_PATH,
|
||||
num_cond_frames=NUM_COND_FRAMES,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(video_path=VIDEO_PATH),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=50,
|
||||
fps=15,
|
||||
guidance_scale=4.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
extensions={"num_cond_frames": NUM_COND_FRAMES},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"\nBasic generation complete! Video saved to: {output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
@@ -118,37 +136,55 @@ def distill_refine_generation():
|
||||
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
|
||||
print("-" * 40)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-VC-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=False,
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
lora_nickname="distilled",
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-VC-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Distilled-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": False,
|
||||
"lora_nickname": "distilled",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
distill_output_path = "outputs_video/longcat_vc_distill"
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
video_path=VIDEO_PATH,
|
||||
num_cond_frames=NUM_COND_FRAMES,
|
||||
output_path=distill_output_path,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(video_path=VIDEO_PATH),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=93,
|
||||
num_inference_steps=16,
|
||||
fps=15,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=distill_output_path,
|
||||
save_video=True,
|
||||
),
|
||||
extensions={"num_cond_frames": NUM_COND_FRAMES},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
|
||||
generator.shutdown()
|
||||
|
||||
@@ -166,41 +202,61 @@ def distill_refine_generation():
|
||||
|
||||
# Create a new generator with refinement LoRA and BSA enabled
|
||||
# Note: Refinement uses the T2V model (not VC) since it's upscaling the generated video
|
||||
refine_generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=False,
|
||||
enable_bsa=True,
|
||||
bsa_sparsity=0.875,
|
||||
bsa_chunk_q=[4, 4, 8],
|
||||
bsa_chunk_k=[4, 4, 8],
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
lora_nickname="refinement",
|
||||
refine_generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LongCat-Video-T2V-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="FastVideo/LongCat-Video-T2V-Refinement-LoRA",
|
||||
),
|
||||
experimental={
|
||||
"enable_bsa": True,
|
||||
"bsa_sparsity": 0.875,
|
||||
"bsa_chunk_q": [4, 4, 8],
|
||||
"bsa_chunk_k": [4, 4, 8],
|
||||
"lora_nickname": "refinement",
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
refine_output_path = "outputs_video/longcat_vc_refine_720p"
|
||||
|
||||
refine_generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
output_path=refine_output_path,
|
||||
save_video=True,
|
||||
refine_from=distill_video_path,
|
||||
t_thresh=0.5,
|
||||
spatial_refine_only=False,
|
||||
num_cond_frames=0, # For refinement, no conditioning frames
|
||||
height=720,
|
||||
width=1280,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
|
||||
refine_generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
negative_prompt=NEGATIVE_PROMPT,
|
||||
inputs=InputConfig(refine_from=distill_video_path),
|
||||
sampling=SamplingConfig(
|
||||
height=720,
|
||||
width=1280,
|
||||
num_inference_steps=50,
|
||||
fps=30,
|
||||
guidance_scale=1.0,
|
||||
seed=SEED,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=refine_output_path,
|
||||
save_video=True,
|
||||
),
|
||||
extensions={
|
||||
"t_thresh": 0.5,
|
||||
"spatial_refine_only": False,
|
||||
"num_cond_frames": 0, # For refinement, no conditioning frames
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
print(f"Refinement complete! Video saved to: {refine_output_path}")
|
||||
refine_generator.shutdown()
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
|
||||
|
||||
PROMPT = (
|
||||
@@ -18,22 +25,32 @@ PROMPT = (
|
||||
|
||||
def main() -> None:
|
||||
# Uses FastVideo default sampling settings for LTX2 base.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Davids048/LTX2-Base-Diffusers",
|
||||
num_gpus=1,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Davids048/LTX2-Base-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
output_path = "outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
num_frames=121,
|
||||
height=1088,
|
||||
width=1920,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
num_frames=121,
|
||||
height=1088,
|
||||
width=1920,
|
||||
),
|
||||
)
|
||||
)
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -49,6 +49,11 @@ from pathlib import Path
|
||||
import torch._inductor.config as _inductor
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig, ComponentConfig, EngineConfig, GenerationRequest,
|
||||
GeneratorConfig, OffloadConfig, OutputConfig, PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig
|
||||
from fastvideo.utils import maybe_download_model
|
||||
|
||||
@@ -86,9 +91,9 @@ PROMPT = os.getenv("LTX23_I2V_PROMPT", DEFAULT_PROMPT)
|
||||
|
||||
# Per-stage timing helpers --------------------------------------------------
|
||||
|
||||
def _print_stage_breakdown(result: dict, label: str) -> float | None:
|
||||
def _print_stage_breakdown(result, label: str) -> float | None:
|
||||
"""Print stage execution times and return the sum, or None if missing."""
|
||||
logging_info = result.get("logging_info")
|
||||
logging_info = result.logging_info
|
||||
stages = getattr(logging_info, "stages", None) if logging_info else None
|
||||
if not stages:
|
||||
print(f" [{label}] stage breakdown unavailable")
|
||||
@@ -104,11 +109,11 @@ def _print_stage_breakdown(result: dict, label: str) -> float | None:
|
||||
|
||||
|
||||
def _collect_stage_times(
|
||||
result: dict,
|
||||
result,
|
||||
stage_times: dict[str, list[float]],
|
||||
stage_order: OrderedDict[str, None],
|
||||
) -> None:
|
||||
logging_info = result.get("logging_info")
|
||||
logging_info = result.logging_info
|
||||
stages = getattr(logging_info, "stages", None) if logging_info else None
|
||||
if not stages:
|
||||
return
|
||||
@@ -169,34 +174,54 @@ def main() -> None:
|
||||
pipeline_config = PipelineConfig.from_pretrained(model_root)
|
||||
pipeline_config.dit_config.quant_config = None
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_root,
|
||||
num_gpus=1,
|
||||
# LTX-2.3 distilled uses the two-stage refine pipeline; the refine
|
||||
# LoRA is intentionally empty for the distilled student.
|
||||
ltx2_refine_enabled=True,
|
||||
ltx2_refine_upsampler_path=str(refine_upsampler_path),
|
||||
ltx2_refine_lora_path="",
|
||||
ltx2_refine_num_inference_steps=3,
|
||||
ltx2_refine_guidance_scale=1.0,
|
||||
ltx2_refine_add_noise=True,
|
||||
pipeline_config=pipeline_config,
|
||||
enable_torch_compile=True,
|
||||
enable_torch_compile_text_encoder=True,
|
||||
# Compile the VAE codec submodules (encoder / decoder) too. The
|
||||
# `LTX2CausalVideoAutoencoder` declares `_compile_conditions` so
|
||||
# `_compile_with_conditions` targets just those submodules and
|
||||
# leaves the surrounding tiling control flow eager — needed for
|
||||
# fullgraph + dynamic=False to succeed. VAE eager decode is
|
||||
# ~1.0s; compiling it brings the stage to ~0.3s.
|
||||
enable_torch_compile_vae=True,
|
||||
torch_compile_kwargs=torch_compile_kwargs,
|
||||
torch_compile_kwargs_vae=torch_compile_kwargs,
|
||||
# Keep everything resident — no CPU offload for serving-style runs.
|
||||
dit_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
ltx2_vae_tiling=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_root,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
compile=CompileConfig(
|
||||
enabled=True,
|
||||
text_encoder_enabled=True,
|
||||
# Compile the VAE codec submodules (encoder / decoder)
|
||||
# too. The `LTX2CausalVideoAutoencoder` declares
|
||||
# `_compile_conditions` so `_compile_with_conditions`
|
||||
# targets just those submodules and leaves the
|
||||
# surrounding tiling control flow eager — needed for
|
||||
# fullgraph + dynamic=False to succeed. VAE eager decode
|
||||
# is ~1.0s; compiling it brings the stage to ~0.3s.
|
||||
vae_enabled=True,
|
||||
backend=torch_compile_kwargs["backend"],
|
||||
fullgraph=torch_compile_kwargs["fullgraph"],
|
||||
mode=torch_compile_kwargs["mode"],
|
||||
dynamic=torch_compile_kwargs["dynamic"],
|
||||
vae_kwargs=torch_compile_kwargs,
|
||||
),
|
||||
# Keep everything resident — no CPU offload for serving runs.
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
text_encoder=False,
|
||||
vae=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
vae_tiling=False,
|
||||
# LTX-2.3 distilled uses the two-stage refine pipeline; the
|
||||
# refine LoRA is intentionally empty for the distilled
|
||||
# student.
|
||||
components=ComponentConfig(
|
||||
upsampler_weights=str(refine_upsampler_path),
|
||||
),
|
||||
preset_overrides={
|
||||
"refine": {
|
||||
"enabled": True,
|
||||
"num_inference_steps": 3,
|
||||
"guidance_scale": 1.0,
|
||||
"add_noise": True,
|
||||
}
|
||||
},
|
||||
experimental={"pipeline_config": pipeline_config},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
common_kwargs = dict(
|
||||
@@ -206,12 +231,15 @@ def main() -> None:
|
||||
height=1280, width=832, # portrait runway aspect
|
||||
num_frames=121, fps=24, # ~5s clip
|
||||
num_inference_steps=8, # distilled denoise steps
|
||||
# i2v: anchor the input image at frame 0 with full strength.
|
||||
# `ltx2_image_crf=0.0` skips an extra JPEG re-encode of an already
|
||||
# JPEG conditioning image.
|
||||
)
|
||||
|
||||
# i2v: anchor the input image at frame 0 with full strength.
|
||||
# `ltx2_image_crf=0.0` skips an extra JPEG re-encode of an already
|
||||
# JPEG conditioning image. These are model-specific knobs routed through
|
||||
# the request extensions escape hatch.
|
||||
common_extensions = dict(
|
||||
ltx2_images=[(I2V_IMAGE, 0, 1.0)],
|
||||
ltx2_image_crf=0.0,
|
||||
save_video=True,
|
||||
)
|
||||
|
||||
warmup_runs = 2
|
||||
@@ -227,10 +255,25 @@ def main() -> None:
|
||||
for w in range(warmup_runs):
|
||||
t0 = time.perf_counter()
|
||||
print(f"\n[warmup {w + 1}/{warmup_runs}] compiling + generating…")
|
||||
generator.generate_video(
|
||||
output_path=str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4"),
|
||||
seed=7,
|
||||
**common_kwargs,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=common_kwargs["prompt"],
|
||||
negative_prompt=common_kwargs["negative_prompt"],
|
||||
sampling=SamplingConfig(
|
||||
guidance_scale=common_kwargs["guidance_scale"],
|
||||
height=common_kwargs["height"],
|
||||
width=common_kwargs["width"],
|
||||
num_frames=common_kwargs["num_frames"],
|
||||
fps=common_kwargs["fps"],
|
||||
num_inference_steps=common_kwargs["num_inference_steps"],
|
||||
seed=7,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4"),
|
||||
save_video=True,
|
||||
),
|
||||
extensions=common_extensions,
|
||||
)
|
||||
)
|
||||
dt = time.perf_counter() - t0
|
||||
warmup_secs.append(dt)
|
||||
@@ -245,19 +288,31 @@ def main() -> None:
|
||||
out_path = OUTPUT_DIR / f"output_ltx2_3_distilled_i2v_run_{m + 1}.mp4"
|
||||
print(f"\n[measured {m + 1}/{measured_runs}] generating: {out_path}")
|
||||
t0 = time.perf_counter()
|
||||
result = generator.generate_video(
|
||||
output_path=str(out_path),
|
||||
seed=2002 + m,
|
||||
**common_kwargs,
|
||||
result = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=common_kwargs["prompt"],
|
||||
negative_prompt=common_kwargs["negative_prompt"],
|
||||
sampling=SamplingConfig(
|
||||
guidance_scale=common_kwargs["guidance_scale"],
|
||||
height=common_kwargs["height"],
|
||||
width=common_kwargs["width"],
|
||||
num_frames=common_kwargs["num_frames"],
|
||||
fps=common_kwargs["fps"],
|
||||
num_inference_steps=common_kwargs["num_inference_steps"],
|
||||
seed=2002 + m,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=str(out_path),
|
||||
save_video=True,
|
||||
),
|
||||
extensions=common_extensions,
|
||||
)
|
||||
)
|
||||
wall = time.perf_counter() - t0
|
||||
e2e = (
|
||||
result.get("e2e_latency")
|
||||
if isinstance(result, dict) else None
|
||||
) or wall
|
||||
e2e = (result.extra.get("e2e_latency") if result is not None else None) or wall
|
||||
measured_secs.append(e2e)
|
||||
print(f"[measured {m + 1}/{measured_runs}] e2e={e2e:.2f}s wall={wall:.2f}s")
|
||||
if isinstance(result, dict):
|
||||
if result is not None:
|
||||
_print_stage_breakdown(result, f"measured {m + 1}")
|
||||
_collect_stage_times(result, stage_times, stage_order)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig
|
||||
|
||||
PROMPT = (
|
||||
"A warm sunny backyard. The camera starts in a tight cinematic close-up "
|
||||
@@ -17,16 +18,19 @@ import os
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "FLASH_ATTN"
|
||||
|
||||
def main() -> None:
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/LTX2-Distilled-Diffusers",
|
||||
num_gpus=4,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/LTX2-Distilled-Diffusers",
|
||||
engine=EngineConfig(num_gpus=4),
|
||||
)
|
||||
)
|
||||
|
||||
output_path = "outputs_video/ltx2_basic/output_ltx2_distilled_t2v.mp4"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(output_path=output_path, save_video=True),
|
||||
)
|
||||
)
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ from pathlib import Path
|
||||
import torch
|
||||
import torch._inductor.config
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig, ComponentConfig, EngineConfig, GenerationRequest,
|
||||
GenerationResult, GeneratorConfig, OffloadConfig, OutputConfig,
|
||||
PipelineSelection, SamplingConfig,
|
||||
)
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig
|
||||
from fastvideo.layers.quantization.nvfp4_config import NVFP4Config
|
||||
from fastvideo.utils import maybe_download_model
|
||||
@@ -45,11 +50,11 @@ def load_validation_entries(path: Path) -> list[dict]:
|
||||
|
||||
|
||||
def print_stage_breakdown(
|
||||
result: dict,
|
||||
result: GenerationResult,
|
||||
run_idx: int,
|
||||
num_runs: int,
|
||||
) -> float | None:
|
||||
logging_info = result.get("logging_info")
|
||||
logging_info = result.logging_info
|
||||
if logging_info is None:
|
||||
print(f"[{run_idx}/{num_runs}] Stage breakdown unavailable: no logging_info")
|
||||
return None
|
||||
@@ -70,9 +75,9 @@ def print_stage_breakdown(
|
||||
|
||||
|
||||
def extract_sr_forward_latency(
|
||||
result: dict,
|
||||
result: GenerationResult,
|
||||
) -> tuple[float | None, list[tuple[str, float]], list[str]]:
|
||||
logging_info = result.get("logging_info")
|
||||
logging_info = result.logging_info
|
||||
if logging_info is None:
|
||||
return None, [], []
|
||||
|
||||
@@ -106,11 +111,11 @@ def extract_sr_forward_latency(
|
||||
|
||||
|
||||
def collect_stage_times(
|
||||
result: dict,
|
||||
result: GenerationResult,
|
||||
stage_times: dict[str, list[float]],
|
||||
stage_order: OrderedDict[str, None],
|
||||
) -> None:
|
||||
logging_info = result.get("logging_info")
|
||||
logging_info = result.logging_info
|
||||
if logging_info is None:
|
||||
return
|
||||
stages = getattr(logging_info, "stages", None)
|
||||
@@ -202,26 +207,45 @@ def main() -> None:
|
||||
"dynamic": False,
|
||||
}
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_root,
|
||||
num_gpus=1,
|
||||
ltx2_refine_enabled=True,
|
||||
ltx2_refine_upsampler_path=str(refine_upsampler_path),
|
||||
refine_lora_path="", # keep refine LoRA disabled in this repo's typed adapter
|
||||
ltx2_refine_lora_path="", # keep refine LoRA disabled for distilled model
|
||||
ltx2_refine_num_inference_steps=2,
|
||||
ltx2_refine_guidance_scale=1.0,
|
||||
ltx2_refine_add_noise=True,
|
||||
pipeline_config=pipeline_config,
|
||||
enable_torch_compile=True,
|
||||
enable_torch_compile_text_encoder=True,
|
||||
enable_torch_compile_vae=True,
|
||||
torch_compile_kwargs=torch_compile_kwargs,
|
||||
torch_compile_kwargs_vae=torch_compile_kwargs,
|
||||
dit_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
ltx2_vae_tiling=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_root,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
text_encoder=False,
|
||||
vae=False,
|
||||
),
|
||||
compile=CompileConfig(
|
||||
enabled=True,
|
||||
text_encoder_enabled=True,
|
||||
vae_enabled=True,
|
||||
backend="inductor",
|
||||
fullgraph=True,
|
||||
dynamic=False,
|
||||
vae_kwargs=torch_compile_kwargs,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
vae_tiling=False,
|
||||
components=ComponentConfig(
|
||||
upsampler_weights=str(refine_upsampler_path),
|
||||
),
|
||||
preset_overrides={
|
||||
"refine": {
|
||||
"enabled": True,
|
||||
"num_inference_steps": 2,
|
||||
"guidance_scale": 1.0,
|
||||
"add_noise": True,
|
||||
}
|
||||
},
|
||||
experimental={
|
||||
"refine_lora_path": "", # keep refine LoRA disabled in this repo's typed adapter
|
||||
"pipeline_config": pipeline_config,
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
run_times: list[float] = []
|
||||
@@ -243,25 +267,31 @@ def main() -> None:
|
||||
torch.cuda.synchronize()
|
||||
|
||||
start = time.perf_counter()
|
||||
result = generator.generate_video(
|
||||
prompt=prompt,
|
||||
output_path=str(output_path),
|
||||
fps=24,
|
||||
seed=10,
|
||||
save_video=True,
|
||||
guidance_scale=1.0,
|
||||
height=benchmark_entry.get("height", 1088),
|
||||
width=benchmark_entry.get("width", 1920),
|
||||
num_frames=121,
|
||||
num_inference_steps=5,
|
||||
# image_path="examples/inference/basic/prompt1.png",
|
||||
# ltx2_image_crf=0.0
|
||||
result = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(
|
||||
fps=24,
|
||||
seed=10,
|
||||
guidance_scale=1.0,
|
||||
height=benchmark_entry.get("height", 1088),
|
||||
width=benchmark_entry.get("width", 1920),
|
||||
num_frames=121,
|
||||
num_inference_steps=5,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=str(output_path),
|
||||
save_video=True,
|
||||
),
|
||||
# inputs=InputConfig(image_path="examples/inference/basic/prompt1.png"),
|
||||
# extensions={"ltx2_image_crf": 0.0},
|
||||
)
|
||||
)
|
||||
if os.environ.get("FASTVIDEO_STAGE_LOGGING") == "0":
|
||||
torch.cuda.synchronize()
|
||||
|
||||
elapsed = result.get("generation_time") if isinstance(result, dict) else None
|
||||
e2e_elapsed = result.get("e2e_latency") if isinstance(result, dict) else None
|
||||
elapsed = result.generation_time if isinstance(result, GenerationResult) else None
|
||||
e2e_elapsed = result.extra.get("e2e_latency") if isinstance(result, GenerationResult) else None
|
||||
if elapsed is None:
|
||||
elapsed = time.perf_counter() - start
|
||||
if e2e_elapsed is None:
|
||||
@@ -272,7 +302,7 @@ def main() -> None:
|
||||
print(f"[{i + 1}/{num_runs}] Generation time: {elapsed:.2f}s")
|
||||
print(f"[{i + 1}/{num_runs}] End-to-end latency: {e2e_elapsed:.2f}s")
|
||||
|
||||
if isinstance(result, dict):
|
||||
if isinstance(result, GenerationResult):
|
||||
stage_sum = print_stage_breakdown(result, i + 1, num_runs)
|
||||
if stage_sum is not None:
|
||||
non_stage_overhead = e2e_elapsed - stage_sum
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig,
|
||||
OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_lucy_edit"
|
||||
|
||||
|
||||
def main():
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"decart-ai/Lucy-Edit-Dev",
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="decart-ai/Lucy-Edit-Dev",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
prompt = ("Change the apron and blouse to a classic clown costume: satin "
|
||||
"polka-dot jumpsuit in bright primary colors, ruffled white collar, "
|
||||
@@ -20,18 +29,20 @@ def main():
|
||||
"foam nose; soft window light from left, eye-level medium shot.")
|
||||
video_path = "https://d2drjpuinn46lb.cloudfront.net/painter_original_edit.mp4"
|
||||
|
||||
generator.generate_video(
|
||||
prompt,
|
||||
negative_prompt="",
|
||||
video_path=video_path,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=81,
|
||||
fps=24,
|
||||
guidance_scale=5.0,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt="",
|
||||
inputs=InputConfig(video_path=video_path),
|
||||
sampling=SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=81,
|
||||
fps=24,
|
||||
guidance_scale=5.0,
|
||||
),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig,
|
||||
SamplingConfig)
|
||||
from fastvideo.models.dits.matrixgame2.utils import create_action_presets
|
||||
|
||||
import torch
|
||||
@@ -38,35 +40,48 @@ def main():
|
||||
# attempt to identify the optimal arguments.
|
||||
config = VARIANT_CONFIG[MODEL_VARIANT]
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
config["model_path"],
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=config["model_path"],
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
num_frames = 597
|
||||
actions = create_action_presets(num_frames, keyboard_dim=config["keyboard_dim"])
|
||||
grid_sizes = torch.tensor([150, 44, 80])
|
||||
|
||||
generator.generate_video(
|
||||
prompt="",
|
||||
image_path=config["image_url"],
|
||||
mouse_cond=actions["mouse"].unsqueeze(0),
|
||||
keyboard_cond=actions["keyboard"].unsqueeze(0),
|
||||
grid_sizes=grid_sizes,
|
||||
num_frames=num_frames,
|
||||
height=352,
|
||||
width=640,
|
||||
num_inference_steps=50,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt="",
|
||||
inputs=InputConfig(
|
||||
image_path=config["image_url"],
|
||||
mouse_cond=actions["mouse"].unsqueeze(0),
|
||||
keyboard_cond=actions["keyboard"].unsqueeze(0),
|
||||
grid_sizes=grid_sizes,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
num_frames=num_frames,
|
||||
height=352,
|
||||
width=640,
|
||||
num_inference_steps=50,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from fastvideo.entrypoints.streaming_generator import StreamingVideoGenerator
|
||||
from fastvideo.models.dits.matrixgame2.utils import get_current_action_async, expand_action_to_frames
|
||||
from fastvideo.api import EngineConfig, GeneratorConfig, OffloadConfig
|
||||
|
||||
import torch
|
||||
import asyncio
|
||||
@@ -42,17 +43,23 @@ async def main():
|
||||
# attempt to identify the optimal arguments.
|
||||
config = VARIANT_CONFIG[MODEL_VARIANT]
|
||||
|
||||
generator = StreamingVideoGenerator.from_pretrained(
|
||||
config["model_path"],
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = StreamingVideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=config["model_path"],
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
max_blocks = 50
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
MODEL_PATH = "FastVideo/Matrix-Game-3.0-Base-Distilled-Diffusers"
|
||||
IMAGE_URL = "https://raw.githubusercontent.com/SkyworkAI/Matrix-Game/main/Matrix-Game-3/demo_images/001/image.png"
|
||||
@@ -7,28 +10,38 @@ OUTPUT_PATH = "video_samples_matrixgame3"
|
||||
|
||||
|
||||
def main():
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
MODEL_PATH,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=MODEL_PATH,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
))
|
||||
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
image_path=IMAGE_URL,
|
||||
height=720,
|
||||
width=1280,
|
||||
num_frames=57,
|
||||
num_inference_steps=3,
|
||||
guidance_scale=1.0,
|
||||
seed=42,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
inputs=InputConfig(image_path=IMAGE_URL),
|
||||
sampling=SamplingConfig(
|
||||
height=720,
|
||||
width=1280,
|
||||
num_frames=57,
|
||||
num_inference_steps=3,
|
||||
guidance_scale=1.0,
|
||||
seed=42,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,40 +1,56 @@
|
||||
from fastvideo import VideoGenerator, PipelineConfig
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
|
||||
def main():
|
||||
config = PipelineConfig.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
config.text_encoder_precisions = ["fp16"]
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
pipeline_config=config,
|
||||
use_fsdp_inference=False, # Disable FSDP for MPS
|
||||
dit_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
disable_autocast=False,
|
||||
num_gpus=1,
|
||||
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # Disable FSDP for MPS
|
||||
disable_autocast=False,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
experimental={"pipeline_config": config},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Create sampling parameters with reduced number of frames
|
||||
sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
sampling_param.num_frames = 25 # Reduce from default 81 to 25 frames bc we have to use the SDPA attn backend for mps
|
||||
sampling_param.height = 256
|
||||
sampling_param.width = 256
|
||||
# Reduce from default 81 to 25 frames bc we have to use the SDPA attn backend for mps
|
||||
sampling = SamplingConfig(
|
||||
num_frames=25,
|
||||
height=256,
|
||||
width=256,
|
||||
)
|
||||
|
||||
prompt = ("A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones.")
|
||||
|
||||
video = generator.generate_video(prompt, sampling_param=sampling_param)
|
||||
|
||||
video = generator.generate(GenerationRequest(prompt=prompt, sampling=sampling))
|
||||
|
||||
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.")
|
||||
|
||||
video2 = generator.generate_video(prompt2, sampling_param=sampling_param)
|
||||
|
||||
video2 = generator.generate(GenerationRequest(prompt=prompt2, sampling=sampling))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig,
|
||||
OutputConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples"
|
||||
def main():
|
||||
@@ -8,17 +10,23 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=2,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
distributed_executor_backend="ray",
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=2,
|
||||
use_fsdp_inference=True,
|
||||
execution_backend="ray",
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Generate videos with the same simple API, regardless of GPU count
|
||||
@@ -27,7 +35,8 @@ def main():
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True)
|
||||
video = generator.generate(
|
||||
GenerationRequest(prompt=prompt, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True)))
|
||||
|
||||
# Generate another video with a different prompt, without reloading the
|
||||
# model!
|
||||
@@ -37,7 +46,8 @@ def main():
|
||||
"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.")
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(prompt=prompt2, output=OutputConfig(output_path=OUTPUT_PATH, save_video=True)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -85,24 +85,35 @@ def main() -> None:
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = args.backend
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig, OutputConfig,
|
||||
ParallelismConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
|
||||
init_kwargs = {
|
||||
"num_gpus": args.num_gpus,
|
||||
"workload_type": "t2i",
|
||||
"sp_size": 1,
|
||||
"tp_size": 1,
|
||||
"dit_cpu_offload": False,
|
||||
"dit_layerwise_offload": False,
|
||||
"text_encoder_cpu_offload": False,
|
||||
"vae_cpu_offload": False,
|
||||
"image_encoder_cpu_offload": False,
|
||||
"pin_cpu_memory": False,
|
||||
"use_fsdp_inference": False,
|
||||
}
|
||||
|
||||
generator = VideoGenerator.from_pretrained(model_path=args.model_path, **init_kwargs)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=args.model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=False,
|
||||
parallelism=ParallelismConfig(
|
||||
sp_size=1,
|
||||
tp_size=1,
|
||||
),
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
dit_layerwise=False,
|
||||
text_encoder=False,
|
||||
vae=False,
|
||||
image_encoder=False,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(workload_type="t2i"),
|
||||
)
|
||||
)
|
||||
try:
|
||||
for i, prompt in enumerate(prompts):
|
||||
seed = args.seed + i
|
||||
@@ -113,20 +124,25 @@ def main() -> None:
|
||||
output_path = os.path.join(args.out_dir, f"{filename_base}.png")
|
||||
print(f"[sd35] prompt_idx={i} seed={seed} output_path={output_path}")
|
||||
|
||||
generation_kwargs = {
|
||||
"output_path": output_path,
|
||||
"height": args.height,
|
||||
"width": args.width,
|
||||
"num_frames": 1,
|
||||
"fps": 1,
|
||||
"num_inference_steps": args.steps,
|
||||
"guidance_scale": args.guidance,
|
||||
"seed": seed,
|
||||
"negative_prompt": args.negative,
|
||||
"save_video": True,
|
||||
}
|
||||
|
||||
generator.generate_video(prompt, **generation_kwargs)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=args.negative,
|
||||
sampling=SamplingConfig(
|
||||
height=args.height,
|
||||
width=args.width,
|
||||
num_frames=1,
|
||||
fps=1,
|
||||
num_inference_steps=args.steps,
|
||||
guidance_scale=args.guidance,
|
||||
seed=seed,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
print(f"[sd35] done. outputs written to: {args.out_dir}")
|
||||
finally:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import os
|
||||
import time
|
||||
from fastvideo import VideoGenerator, SamplingParam
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_causal"
|
||||
def main():
|
||||
@@ -9,23 +17,33 @@ def main():
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
model_name = "wlsaidhi/SFWan2.1-T2V-1.3B-Diffusers"
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_name,
|
||||
generator_config = GeneratorConfig(
|
||||
model_path=model_name,
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
text_encoder_cpu_offload=False,
|
||||
dit_cpu_offload=False,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
text_encoder=False,
|
||||
dit=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
sampling_param = SamplingParam.from_pretrained(model_name)
|
||||
generator = VideoGenerator.from_config(generator_config)
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
|
||||
request = GenerationRequest(
|
||||
prompt=prompt,
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
video = generator.generate(request)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
# NOTE: This is still a work in progress, and the checkpoints are not released yet.
|
||||
|
||||
from fastvideo import VideoGenerator, SamplingParam
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
InputConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
import json
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
|
||||
OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_i2v"
|
||||
def main():
|
||||
@@ -10,26 +19,37 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
dit_precision="fp32",
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125],
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
experimental={
|
||||
"dit_precision": "fp32",
|
||||
"dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125],
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
sampling_param = SamplingParam.from_pretrained("FastVideo/SFWan2.2-I2V-A14B-Preview-Diffusers")
|
||||
sampling_param.num_frames = 81
|
||||
sampling_param.width = 832
|
||||
sampling_param.height = 480
|
||||
sampling_param.seed = 1000
|
||||
sampling = SamplingConfig(
|
||||
num_frames=81,
|
||||
width=832,
|
||||
height=480,
|
||||
seed=1000,
|
||||
)
|
||||
|
||||
with open("assets/prompts/mixkit_i2v.jsonl", "r") as f:
|
||||
prompt_image_pairs = json.load(f)
|
||||
@@ -37,7 +57,14 @@ def main():
|
||||
for prompt_image_pair in prompt_image_pairs:
|
||||
prompt = prompt_image_pair["prompt"]
|
||||
image_path = prompt_image_pair["image_path"]
|
||||
_ = generator.generate_video(prompt, image_path=image_path, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
sampling=sampling,
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# NOTE: This is still a work in progress, and the checkpoints are not released yet.
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_self_forcing_causal_wan2_2_14B_t2v"
|
||||
def main():
|
||||
@@ -10,34 +12,49 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"rand0nmr/SFWan2.2-T2V-A14B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125],
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
init_weights_from_safetensors="/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_inference_transformer/",
|
||||
init_weights_from_safetensors_2="/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_2_inference_transformer/",
|
||||
num_frame_per_block=7,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="rand0nmr/SFWan2.2-T2V-A14B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
transformer_weights="/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_inference_transformer/",
|
||||
transformer_2_weights="/mnt/sharefs/users/hao.zhang/wei/SFwan2.2_distill_self_forcing_release_cfg2/checkpoint-246_weight_only/generator_2_inference_transformer/",
|
||||
),
|
||||
experimental={
|
||||
"dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125],
|
||||
"num_frame_per_block": 7,
|
||||
},
|
||||
),
|
||||
# image_encoder_cpu_offload=False,
|
||||
)
|
||||
)
|
||||
|
||||
# sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
# sampling_param.num_frames = 45
|
||||
# sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
|
||||
# Generate videos with the same simple API, regardless of GPU count
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
_ = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, num_frames=81)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -51,25 +51,31 @@ Prerequisites:
|
||||
uv pip install k_diffusion einops_exts alias_free_torch torchsde
|
||||
"""
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OutputConfig)
|
||||
|
||||
PROMPT = "Lo-fi hip hop instrumental with vinyl crackle and gentle piano."
|
||||
|
||||
|
||||
def main() -> None:
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
num_gpus=1,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
))
|
||||
output_path = "outputs_audio/stable_audio_basic/output_stable_audio.wav"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
# 6-second clip; the model max is ~47.5s.
|
||||
audio_end_in_s=6.0,
|
||||
# The registered preset gives 100 steps + CFG=7.0 by default;
|
||||
# override num_inference_steps / guidance_scale here for QA.
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
# 6-second clip; the model max is ~47.5s.
|
||||
extensions={"audio_end_in_s": 6.0},
|
||||
# The registered preset gives 100 steps + CFG=7.0 by default;
|
||||
# override num_inference_steps / guidance_scale here for QA.
|
||||
))
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,12 @@ Picking `init_audio_strength` (0.0 to 1.0):
|
||||
Prerequisites: same as `basic_stable_audio.py`.
|
||||
"""
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OutputConfig,
|
||||
)
|
||||
|
||||
PROMPT = "Change the piano to a cello playing the same notes"
|
||||
# Path to any audio-bearing file (wav, mp3, mp4, m4a, flac, ...).
|
||||
@@ -58,18 +64,24 @@ INIT_AUDIO_STRENGTH = 0.6
|
||||
|
||||
|
||||
def main() -> None:
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
num_gpus=1,
|
||||
)
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path="outputs_audio/stable_audio_a2a/output_a2a.wav",
|
||||
save_video=True,
|
||||
audio_end_in_s=6.0,
|
||||
init_audio=INIT_AUDIO_PATH,
|
||||
init_audio_strength=INIT_AUDIO_STRENGTH,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
))
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path="outputs_audio/stable_audio_a2a/output_a2a.wav",
|
||||
save_video=True,
|
||||
),
|
||||
extensions={
|
||||
"audio_end_in_s": 6.0,
|
||||
"init_audio": INIT_AUDIO_PATH,
|
||||
"init_audio_strength": INIT_AUDIO_STRENGTH,
|
||||
},
|
||||
))
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ Prerequisites: same as `basic_stable_audio.py`.
|
||||
import os
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GeneratorConfig, GenerationRequest, OutputConfig,
|
||||
)
|
||||
|
||||
PROMPT = "Steady lo-fi hip hop drum loop with vinyl crackle."
|
||||
# Required: path to the reference audio file (wav, mp3, mp4, m4a, flac,
|
||||
@@ -64,19 +67,25 @@ def main() -> None:
|
||||
f"REFERENCE_AUDIO_PATH={REFERENCE_AUDIO_PATH!r} does not exist. "
|
||||
"Edit this script to point at a real audio file (wav/mp3/mp4/"
|
||||
"m4a/flac) before running.")
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
num_gpus=1,
|
||||
)
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path="outputs_audio/stable_audio_inpaint/output_inpaint.wav",
|
||||
save_video=True,
|
||||
audio_end_in_s=TOTAL_SECONDS,
|
||||
inpaint_audio=REFERENCE_AUDIO_PATH,
|
||||
# Tuple form: keep first KEEP_SECONDS, regenerate the rest.
|
||||
inpaint_mask=(KEEP_SECONDS, TOTAL_SECONDS),
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/stable-audio-open-1.0-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
))
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(
|
||||
output_path="outputs_audio/stable_audio_inpaint/output_inpaint.wav",
|
||||
save_video=True,
|
||||
),
|
||||
extensions={
|
||||
"audio_end_in_s": TOTAL_SECONDS,
|
||||
"inpaint_audio": REFERENCE_AUDIO_PATH,
|
||||
# Tuple form: keep first KEEP_SECONDS, regenerate the rest.
|
||||
"inpaint_mask": (KEEP_SECONDS, TOTAL_SECONDS),
|
||||
},
|
||||
))
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
|
||||
@@ -28,24 +28,27 @@ Prerequisites: same as `basic_stable_audio.py`. The converted repo is
|
||||
public so no gated-access flow is required.
|
||||
"""
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OutputConfig)
|
||||
|
||||
PROMPT = "Lo-fi hip hop instrumental with vinyl crackle and gentle piano."
|
||||
|
||||
|
||||
def main() -> None:
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"FastVideo/stable-audio-open-small-Diffusers",
|
||||
num_gpus=1,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="FastVideo/stable-audio-open-small-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
))
|
||||
output_path = "outputs_audio/stable_audio_small/output_stable_audio_small.wav"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
# Small variant trains on a ~11.9s window — keep `audio_end_in_s`
|
||||
# at or below that.
|
||||
audio_end_in_s=6.0,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(output_path=output_path, save_video=True),
|
||||
# Small variant trains on a ~11.9s window — keep `audio_end_in_s`
|
||||
# at or below that.
|
||||
extensions={"audio_end_in_s": 6.0},
|
||||
))
|
||||
generator.shutdown()
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import os
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN"
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_turbodiffusion"
|
||||
|
||||
@@ -11,14 +14,17 @@ OUTPUT_PATH = "video_samples_turbodiffusion"
|
||||
def main() -> None:
|
||||
# TurboDiffusion: 1-4 step video generation using RCM scheduler + SLA attention
|
||||
# FastVideo will automatically use TurboDiffusionPipeline when specified
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"loayrashid/TurboWan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
|
||||
# set to false if using RTX 4090
|
||||
# pin_cpu_memory=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="loayrashid/TurboWan2.1-T2V-1.3B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
),
|
||||
# set to false if using RTX 4090
|
||||
# pin_cpu_memory=False,
|
||||
)
|
||||
)
|
||||
|
||||
# Generate videos with the same simple API, regardless of GPU count
|
||||
@@ -28,11 +34,17 @@ def main() -> None:
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
seed=42,
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
seed=42,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# Generate another video with a different prompt, without reloading the model!
|
||||
@@ -43,11 +55,17 @@ def main() -> None:
|
||||
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
|
||||
"cinematic."
|
||||
)
|
||||
video2 = generator.generate_video(
|
||||
prompt2,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
seed=42,
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
sampling=SamplingConfig(
|
||||
seed=42,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import os
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN"
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_turbodiffusion_14B"
|
||||
|
||||
@@ -11,10 +14,12 @@ OUTPUT_PATH = "video_samples_turbodiffusion_14B"
|
||||
def main() -> None:
|
||||
# TurboDiffusion 14B: 1-4 step video generation using RCM scheduler + SLA attention
|
||||
# FastVideo will automatically use TurboDiffusionPipeline when specified
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"loayrashid/TurboWan2.1-T2V-14B-Diffusers",
|
||||
# 14B model needs more GPUs
|
||||
num_gpus=2,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="loayrashid/TurboWan2.1-T2V-14B-Diffusers",
|
||||
# 14B model needs more GPUs
|
||||
engine=EngineConfig(num_gpus=2),
|
||||
)
|
||||
)
|
||||
|
||||
prompt = (
|
||||
@@ -22,11 +27,12 @@ def main() -> None:
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
seed=42,
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
sampling=SamplingConfig(seed=42),
|
||||
)
|
||||
)
|
||||
|
||||
# Generate another video with a different prompt, without reloading the model!
|
||||
@@ -37,11 +43,12 @@ def main() -> None:
|
||||
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
|
||||
"cinematic."
|
||||
)
|
||||
video2 = generator.generate_video(
|
||||
prompt2,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
seed=42,
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
sampling=SamplingConfig(seed=42),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,10 @@ import os
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "SLA_ATTN"
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig,
|
||||
OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
# Use local model path
|
||||
MODEL_PATH = "loayrashid/TurboWan2.2-I2V-A14B-Diffusers"
|
||||
@@ -12,9 +16,11 @@ OUTPUT_PATH = "video_samples_turbodiffusion_i2v"
|
||||
|
||||
def main() -> None:
|
||||
# TurboDiffusion I2V: 1-4 step image-to-video generation
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
MODEL_PATH,
|
||||
num_gpus=2,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=MODEL_PATH,
|
||||
engine=EngineConfig(num_gpus=2),
|
||||
)
|
||||
)
|
||||
|
||||
# Example prompt and image for I2V
|
||||
@@ -24,12 +30,13 @@ def main() -> None:
|
||||
image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG"
|
||||
|
||||
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
image_path=image_path,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
seed=42,
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
sampling=SamplingConfig(seed=42),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig,
|
||||
OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_wan2_2_14B_t2v"
|
||||
def main():
|
||||
@@ -8,30 +10,37 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=2,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.2-T2V-A14B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=2,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
|
||||
# sampling_param.num_frames = 45
|
||||
# sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
|
||||
# Generate videos with the same simple API, regardless of GPU count
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
"wide with interest. The playful yet serene atmosphere is complemented by soft "
|
||||
"natural light filtering through the petals. Mid-shot, warm and cheerful tones."
|
||||
)
|
||||
_ = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, height=720, width=1280, num_frames=81)
|
||||
# video = generator.generate_video(prompt, sampling_param=sampling_param, output_path="wan_t2v_videos/")
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(height=720, width=1280, num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
# Generate another video with a different prompt, without reloading the
|
||||
# model!
|
||||
@@ -41,8 +50,14 @@ def main():
|
||||
"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.")
|
||||
_ = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, height=720, width=1280, num_frames=81)
|
||||
_ = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
sampling=SamplingConfig(height=720, width=1280, num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
InputConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_wan2_1_Fun"
|
||||
OUTPUT_NAME = "wan2.1_test"
|
||||
@@ -9,18 +15,24 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"IRMChen/Wan2.1-Fun-1.3B-Control-Diffusers",
|
||||
# "alibaba-pai/Wan2.2-Fun-A14B-Control",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="IRMChen/Wan2.1-Fun-1.3B-Control-Diffusers",
|
||||
# "alibaba-pai/Wan2.2-Fun-A14B-Control",
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
prompt = "一位年轻女性穿着一件粉色的连衣裙,裙子上有白色的装饰和粉色的纽扣。她的头发是紫色的,头上戴着一个红色的大蝴蝶结,显得非常可爱和精致。她还戴着一个红色的领结,整体造型充满了少女感和活力。她的表情温柔,双手轻轻交叉放在身前,姿态优雅。背景是简单的灰色,没有任何多余的装饰,使得人物更加突出。她的妆容清淡自然,突显了她的清新气质。整体画面给人一种甜美、梦幻的感觉,仿佛置身于童话世界中。"
|
||||
@@ -30,7 +42,14 @@ def main():
|
||||
image_path = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset_Wan2_2/v1.0/8.png"
|
||||
control_video_path = "https://pai-aigc-photog.oss-cn-hangzhou.aliyuncs.com/wan_fun/asset_Wan2_2/v1.0/pose.mp4"
|
||||
|
||||
video = generator.generate_video(prompt, negative_prompt=negative_prompt, image_path=image_path, video_path=control_video_path, output_path=OUTPUT_PATH, output_video_name=OUTPUT_NAME, save_video=True)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
inputs=InputConfig(image_path=image_path, video_path=control_video_path),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, output_video_name=OUTPUT_NAME, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from fastvideo import VideoGenerator
|
||||
|
||||
# from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig,
|
||||
OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_wan2_2_14B_i2v"
|
||||
def main():
|
||||
@@ -8,23 +10,36 @@ def main():
|
||||
# model.
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True, # DiT need to be offloaded for MoE
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.2-I2V-A14B-Diffusers",
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True, # DiT need to be offloaded for MoE
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
# Set pin_cpu_memory to false if CPU RAM is limited and there're no frequent CPU-GPU transfer
|
||||
pin_cpu_memory=True,
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside."
|
||||
image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG"
|
||||
|
||||
video = generator.generate_video(prompt, image_path=image_path, output_path=OUTPUT_PATH, save_video=True, height=832, width=480, num_frames=81)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
sampling=SamplingConfig(height=832, width=480, num_frames=81),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, InputConfig, OffloadConfig, OutputConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples_wan2_2_5B_ti2v"
|
||||
def main():
|
||||
@@ -7,22 +10,34 @@ def main():
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
model_name = "Wan-AI/Wan2.2-TI2V-5B-Diffusers"
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_name,
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder_cpu_offload=False,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_name,
|
||||
engine=EngineConfig(
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=False, # set to True if GPU is out of memory
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
# image_encoder=False,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# I2V is triggered just by passing in an image_path argument
|
||||
prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside."
|
||||
image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG"
|
||||
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, image_path=image_path)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
# Generate another video with a different prompt, without reloading the
|
||||
# model!
|
||||
@@ -34,8 +49,13 @@ def main():
|
||||
"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.")
|
||||
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
|
||||
video2 = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt2,
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -50,23 +50,33 @@ N_DUP = 4 # how many times to duplicate the video for the gen/ref corpora
|
||||
def generate_one_ltx2_video() -> str:
|
||||
os.environ.setdefault("FASTVIDEO_ATTENTION_BACKEND", "FLASH_ATTN")
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OutputConfig, SamplingConfig)
|
||||
|
||||
Path(OUTPUT_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||
# Davids048/LTX2-Base-Diffusers is the audio-capable LTX-2 checkpoint
|
||||
# (the Distilled variant ships without the audio VAE, so its mp4
|
||||
# audio track is silence/noise — unusable for audio.* metrics).
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Davids048/LTX2-Base-Diffusers",
|
||||
num_gpus=1,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Davids048/LTX2-Base-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
)
|
||||
)
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
num_frames=121, # ~5s @ 24 fps — long enough for audio.desync (Synchformer ≥14 segments)
|
||||
height=480,
|
||||
width=832,
|
||||
fps=24,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
sampling=SamplingConfig(
|
||||
num_frames=121, # ~5s @ 24 fps — long enough for audio.desync (Synchformer ≥14 segments)
|
||||
height=480,
|
||||
width=832,
|
||||
fps=24,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
generator.shutdown()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
@@ -21,6 +21,10 @@ Install: ``uv pip install -e .[eval-audio]`` covers both metrics here
|
||||
import torch
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.eval import create_evaluator
|
||||
|
||||
PROMPT = (
|
||||
@@ -39,20 +43,26 @@ METRICS = [
|
||||
|
||||
|
||||
def main() -> None:
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Davids048/LTX2-Base-Diffusers",
|
||||
num_gpus=1,
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Davids048/LTX2-Base-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
))
|
||||
|
||||
output_path = "outputs_video/ltx2_audio_eval/output.mp4"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
num_frames=121,
|
||||
height=1088,
|
||||
width=1920,
|
||||
)
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
sampling=SamplingConfig(
|
||||
num_frames=121,
|
||||
height=1088,
|
||||
width=1920,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
),
|
||||
))
|
||||
generator.shutdown()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ sharing, or run on a smaller-resolution generation.
|
||||
import torch
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.eval import Evaluator
|
||||
from fastvideo.eval.io import build_eval_kwargs
|
||||
|
||||
@@ -58,19 +62,20 @@ METRICS = [
|
||||
|
||||
def main() -> None:
|
||||
# ----- generation (matches examples/inference/basic/basic_ltx2.py) -----
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Davids048/LTX2-Base-Diffusers",
|
||||
num_gpus=1,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Davids048/LTX2-Base-Diffusers",
|
||||
engine=EngineConfig(num_gpus=1),
|
||||
)
|
||||
)
|
||||
|
||||
output_path = "outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4"
|
||||
generator.generate_video(
|
||||
prompt=PROMPT,
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
num_frames=121,
|
||||
height=1088,
|
||||
width=1920,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
output=OutputConfig(output_path=output_path, save_video=True),
|
||||
sampling=SamplingConfig(num_frames=121, height=1088, width=1920),
|
||||
)
|
||||
)
|
||||
generator.shutdown()
|
||||
# Free residual CUDA memory the generator left behind so the
|
||||
|
||||
@@ -45,6 +45,9 @@ def _generate_videos(rows: list[dict], videos_dir: Path,
|
||||
model: str, num_gpus: int,
|
||||
num_frames: int, height: int, width: int) -> None:
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
videos_dir.mkdir(parents=True, exist_ok=True)
|
||||
todo = [(row, videos_dir / _expected_filename(row)) for row in rows]
|
||||
@@ -55,13 +58,16 @@ def _generate_videos(rows: list[dict], videos_dir: Path,
|
||||
|
||||
print(f"[gen] {len(todo)}/{len(rows)} scenarios to render with {model} "
|
||||
f"({num_frames}x{height}x{width})...")
|
||||
gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus)
|
||||
gen = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=model, engine=EngineConfig(num_gpus=num_gpus),
|
||||
))
|
||||
try:
|
||||
for row, out_path in todo:
|
||||
gen.generate_video(
|
||||
prompt=row["prompt"], output_path=str(out_path), save_video=True,
|
||||
num_frames=num_frames, height=height, width=width,
|
||||
)
|
||||
gen.generate(GenerationRequest(
|
||||
prompt=row["prompt"],
|
||||
sampling=SamplingConfig(num_frames=num_frames, height=height, width=width),
|
||||
output=OutputConfig(output_path=str(out_path), save_video=True),
|
||||
))
|
||||
finally:
|
||||
gen.shutdown()
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ def _generate_videos(prompts: list[str], videos_dir: Path,
|
||||
model: str, num_gpus: int,
|
||||
num_frames: int, height: int, width: int) -> None:
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OutputConfig, SamplingConfig)
|
||||
|
||||
videos_dir.mkdir(parents=True, exist_ok=True)
|
||||
todo = [(p, videos_dir / f"{_slugify(p)}.mp4") for p in prompts]
|
||||
@@ -53,13 +55,15 @@ def _generate_videos(prompts: list[str], videos_dir: Path,
|
||||
|
||||
print(f"[gen] {len(todo)}/{len(prompts)} prompts to render with {model} "
|
||||
f"({num_frames}x{height}x{width})...")
|
||||
gen = VideoGenerator.from_pretrained(model, num_gpus=num_gpus)
|
||||
gen = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=model, engine=EngineConfig(num_gpus=num_gpus)))
|
||||
try:
|
||||
for prompt, out_path in todo:
|
||||
gen.generate_video(
|
||||
prompt=prompt, output_path=str(out_path), save_video=True,
|
||||
num_frames=num_frames, height=height, width=width,
|
||||
)
|
||||
gen.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_frames=num_frames, height=height, width=width),
|
||||
output=OutputConfig(output_path=str(out_path), save_video=True),
|
||||
))
|
||||
finally:
|
||||
gen.shutdown()
|
||||
|
||||
|
||||
@@ -33,6 +33,13 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.eval import create_evaluator
|
||||
from fastvideo.eval.io import load_video
|
||||
|
||||
@@ -99,16 +106,27 @@ def generate(args: argparse.Namespace) -> Path:
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(f"[gen] loading {args.model} ({args.num_gpus} GPU)...")
|
||||
generator = VideoGenerator.from_pretrained(args.model, num_gpus=args.num_gpus)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=args.model,
|
||||
engine=EngineConfig(num_gpus=args.num_gpus),
|
||||
)
|
||||
)
|
||||
try:
|
||||
print(f"[gen] generating to {out}...")
|
||||
generator.generate_video(
|
||||
prompt=args.prompt,
|
||||
output_path=str(out),
|
||||
save_video=True,
|
||||
num_frames=args.num_frames,
|
||||
height=args.height,
|
||||
width=args.width,
|
||||
generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=args.prompt,
|
||||
sampling=SamplingConfig(
|
||||
num_frames=args.num_frames,
|
||||
height=args.height,
|
||||
width=args.width,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=str(out),
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
finally:
|
||||
generator.shutdown()
|
||||
|
||||
@@ -33,7 +33,7 @@ This demo initializes a `VideoGenerator` with the minimum required arguments for
|
||||
|
||||
The core functionality is in the `generate_video` function, which:
|
||||
1. Processes user inputs
|
||||
2. Uses the FastVideo VideoGenerator from earlier to run inference (`generator.generate_video()`)
|
||||
2. Uses the FastVideo VideoGenerator from earlier to run inference (`generator.generate(GenerationRequest(...))`)
|
||||
|
||||
## Gradio Interface
|
||||
|
||||
|
||||
@@ -5,7 +5,13 @@ import time
|
||||
|
||||
import gradio as gr
|
||||
from fastvideo.entrypoints.video_generator import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
SamplingParam,
|
||||
)
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
@@ -129,9 +135,22 @@ def create_gradio_interface(default_params: dict[str, SamplingParam], generators
|
||||
output_dir = "outputs/"
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
start_time = time.time()
|
||||
result = generator.generate_video(prompt=prompt, sampling_param=params, save_video=True, return_frames=False)
|
||||
result = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=params.negative_prompt,
|
||||
sampling=SamplingConfig(
|
||||
seed=int(params.seed),
|
||||
guidance_scale=params.guidance_scale,
|
||||
num_frames=int(params.num_frames),
|
||||
height=int(params.height),
|
||||
width=int(params.width),
|
||||
),
|
||||
output=OutputConfig(save_video=True, return_frames=False),
|
||||
)
|
||||
)
|
||||
inference_time = time.time() - start_time
|
||||
logging_info = result.get("logging_info", None)
|
||||
logging_info = result.logging_info
|
||||
if logging_info:
|
||||
stage_names = logging_info.get_execution_order()
|
||||
stage_execution_times = [
|
||||
@@ -550,7 +569,7 @@ def main():
|
||||
for model_path in model_paths:
|
||||
print(f"Loading model: {model_path}")
|
||||
setup_model_environment(model_path)
|
||||
generators[model_path] = VideoGenerator.from_pretrained(model_path)
|
||||
generators[model_path] = VideoGenerator.from_config(GeneratorConfig(model_path=model_path))
|
||||
default_params[model_path] = SamplingParam.from_pretrained(model_path)
|
||||
demo = create_gradio_interface(default_params, generators)
|
||||
print(f"Starting Gradio frontend at http://{args.host}:{args.port}")
|
||||
|
||||
@@ -55,10 +55,11 @@ demo can actually boot:
|
||||
`fastvideo/fastvideo_args.py` currently wires only `ltx2_vae_tiling`.
|
||||
The backing stages (`ltx2_refine.py`, `ltx2_i2v_conditioning.py`) are
|
||||
also missing from `fastvideo/pipelines/stages/`.
|
||||
3. **`fastvideo.configs.sample.base.SamplingParam`** — the import path used
|
||||
by this demo. Upstream moved sampling params to
|
||||
`fastvideo.api.sampling_param`. A re-export shim at the old path, or an
|
||||
import update here once the other two prereqs land, will resolve it.
|
||||
3. **`SamplingParam`** — now imported from `fastvideo.api` (the public
|
||||
re-export of `fastvideo.api.sampling_param`); the old
|
||||
`fastvideo.configs.sample.base` path was removed upstream. `SamplingParam`
|
||||
here only sources model-default slider values — generation itself runs
|
||||
through the typed `GenerationRequest` / `generator.generate(...)` path.
|
||||
|
||||
## Environment variables
|
||||
|
||||
|
||||
@@ -4,8 +4,16 @@ from pathlib import Path
|
||||
|
||||
import gradio as gr
|
||||
|
||||
from fastvideo.api import (
|
||||
CompileConfig,
|
||||
ComponentConfig,
|
||||
EngineConfig,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
PipelineSelection,
|
||||
SamplingParam,
|
||||
)
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig
|
||||
from fastvideo.configs.sample.base import SamplingParam
|
||||
from fastvideo.entrypoints.video_generator import VideoGenerator
|
||||
from fastvideo.layers.quantization.fp4_config import FP4Config
|
||||
from fastvideo.utils import maybe_download_model
|
||||
@@ -48,28 +56,44 @@ def main():
|
||||
refine_upsampler_path = resolve_refine_upsampler_path(resolved_model_path)
|
||||
print(f"Using refine upsampler: {refine_upsampler_path}")
|
||||
|
||||
generators[model_path] = VideoGenerator.from_pretrained(
|
||||
str(resolved_model_path),
|
||||
num_gpus=1,
|
||||
ltx2_refine_enabled=True,
|
||||
ltx2_refine_upsampler_path=str(refine_upsampler_path),
|
||||
ltx2_refine_lora_path="", # disable refine LoRA for distilled model
|
||||
ltx2_refine_num_inference_steps=2,
|
||||
ltx2_refine_guidance_scale=1.0,
|
||||
ltx2_refine_add_noise=True,
|
||||
pipeline_config=pipeline_config,
|
||||
enable_torch_compile=True,
|
||||
enable_torch_compile_text_encoder=True,
|
||||
torch_compile_kwargs={
|
||||
"backend": "inductor",
|
||||
"fullgraph": True,
|
||||
"mode": "max-autotune-no-cudagraphs",
|
||||
"dynamic": False,
|
||||
},
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
ltx2_vae_tiling=False,
|
||||
generators[model_path] = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=str(resolved_model_path),
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=False,
|
||||
),
|
||||
compile=CompileConfig(
|
||||
enabled=True,
|
||||
text_encoder_enabled=True,
|
||||
backend="inductor",
|
||||
fullgraph=True,
|
||||
mode="max-autotune-no-cudagraphs",
|
||||
dynamic=False,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
upsampler_weights=str(refine_upsampler_path),
|
||||
# Empty refine LoRA path (distilled needs none) -> omit.
|
||||
),
|
||||
vae_tiling=False,
|
||||
preset_overrides={
|
||||
"refine": {
|
||||
"enabled": True,
|
||||
"num_inference_steps": 2,
|
||||
"guidance_scale": 1.0,
|
||||
"add_noise": True,
|
||||
},
|
||||
},
|
||||
# PipelineConfig object (with FP4 quant wired on above) has
|
||||
# no first-class typed field; route via experimental.
|
||||
experimental={"pipeline_config": pipeline_config},
|
||||
),
|
||||
)
|
||||
)
|
||||
default_params[model_path] = apply_ltx2_defaults(
|
||||
SamplingParam.from_pretrained(str(resolved_model_path))
|
||||
|
||||
@@ -4,7 +4,7 @@ from pathlib import Path
|
||||
import torch
|
||||
import torch._inductor.config
|
||||
|
||||
from fastvideo.configs.sample.base import SamplingParam
|
||||
from fastvideo.api import SamplingParam
|
||||
|
||||
LOCAL_DEMO_DIR = Path(__file__).resolve().parent
|
||||
CLASSIFIER_DIR = Path(
|
||||
|
||||
@@ -5,8 +5,14 @@ from copy import deepcopy
|
||||
|
||||
import gradio as gr
|
||||
|
||||
from fastvideo.configs.sample.base import SamplingParam
|
||||
from fastvideo.entrypoints.video_generator import VideoGenerator
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
GenerationRequest,
|
||||
InputConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
SamplingParam,
|
||||
)
|
||||
|
||||
from .config import (
|
||||
DEFAULT_FPS,
|
||||
@@ -69,40 +75,38 @@ def create_gradio_interface(default_params: dict[str, SamplingParam], generators
|
||||
output_path = str(OUTPUT_DIR / video_filename)
|
||||
params.output_path = output_path
|
||||
start_time = time.perf_counter()
|
||||
result = generator.generate_video(
|
||||
prompt=prompt,
|
||||
output_path=output_path,
|
||||
fps=DEFAULT_FPS,
|
||||
seed=int(params.seed),
|
||||
save_video=True,
|
||||
return_frames=False,
|
||||
guidance_scale=float(params.guidance_scale),
|
||||
height=int(params.height),
|
||||
width=int(params.width),
|
||||
num_frames=int(params.num_frames),
|
||||
num_inference_steps=DEFAULT_NUM_INFERENCE_STEPS,
|
||||
negative_prompt=params.negative_prompt,
|
||||
image_path=params.image_path,
|
||||
ltx2_image_crf=0.0
|
||||
result = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=params.negative_prompt,
|
||||
inputs=InputConfig(image_path=params.image_path),
|
||||
sampling=SamplingConfig(
|
||||
seed=int(params.seed),
|
||||
fps=DEFAULT_FPS,
|
||||
guidance_scale=float(params.guidance_scale),
|
||||
height=int(params.height),
|
||||
width=int(params.width),
|
||||
num_frames=int(params.num_frames),
|
||||
num_inference_steps=DEFAULT_NUM_INFERENCE_STEPS,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=output_path,
|
||||
save_video=True,
|
||||
return_frames=False,
|
||||
),
|
||||
# LTX-2 i2v knob without a first-class typed field yet.
|
||||
extensions={"ltx2_image_crf": 0.0},
|
||||
)
|
||||
)
|
||||
wall_time = time.perf_counter() - start_time
|
||||
generation_time = (
|
||||
result.get("generation_time")
|
||||
if isinstance(result, dict) else None
|
||||
)
|
||||
e2e_latency = (
|
||||
result.get("e2e_latency")
|
||||
if isinstance(result, dict) else None
|
||||
)
|
||||
generation_time = result.generation_time
|
||||
e2e_latency = result.extra.get("e2e_latency")
|
||||
if generation_time is None:
|
||||
generation_time = wall_time
|
||||
if e2e_latency is None:
|
||||
e2e_latency = wall_time
|
||||
resolved_output_path = (
|
||||
result.get("output_path", output_path)
|
||||
if isinstance(result, dict) else output_path
|
||||
)
|
||||
logging_info = result.get("logging_info", None) if isinstance(result, dict) else None
|
||||
resolved_output_path = result.video_path or output_path
|
||||
logging_info = result.logging_info
|
||||
if logging_info:
|
||||
stage_names = logging_info.get_execution_order()
|
||||
stage_execution_times = [
|
||||
|
||||
@@ -9,6 +9,7 @@ import uvicorn
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
|
||||
from fastvideo.api import EngineConfig, GeneratorConfig, OffloadConfig
|
||||
from fastvideo.entrypoints.streaming_generator import StreamingVideoGenerator
|
||||
from fastvideo.models.dits.matrixgame2.utils import expand_action_to_frames
|
||||
|
||||
@@ -572,14 +573,20 @@ def main():
|
||||
|
||||
print(f"Loading model: {model_path}")
|
||||
setup_model_environment(model_path)
|
||||
generator = StreamingVideoGenerator.from_pretrained(
|
||||
model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True,
|
||||
generator = StreamingVideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
generators = {model_path: generator}
|
||||
|
||||
@@ -3,7 +3,6 @@ import os
|
||||
import torch
|
||||
import base64
|
||||
import io
|
||||
from copy import deepcopy
|
||||
from typing import Dict, Any, Optional, List
|
||||
import signal
|
||||
import sys
|
||||
@@ -20,6 +19,17 @@ import imageio
|
||||
from ray.serve.handle import DeploymentHandle
|
||||
from prometheus_client import Counter, Histogram, generate_latest
|
||||
|
||||
from fastvideo.api import (
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
InputConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
|
||||
NUM_GPUS = 16
|
||||
DEFAULT_FPS = 16
|
||||
SEED_RANGE_MAX = 1_000_000
|
||||
@@ -136,10 +146,10 @@ def setup_model_environment(model_path: str) -> None:
|
||||
|
||||
|
||||
def process_generation_result(result: Any) -> tuple[List[np.ndarray], float, List[str], List[float]]:
|
||||
frames = result if isinstance(result, list) else result.get("frames", [])
|
||||
generation_time = result.get("generation_time", 0.0) if isinstance(result, dict) else 0.0
|
||||
|
||||
logging_info = result.get("logging_info", None)
|
||||
frames = result.frames or []
|
||||
generation_time = result.generation_time or 0.0
|
||||
|
||||
logging_info = result.logging_info
|
||||
if logging_info:
|
||||
stage_names = logging_info.get_execution_order()
|
||||
stage_execution_times = [
|
||||
@@ -153,24 +163,29 @@ def process_generation_result(result: Any) -> tuple[List[np.ndarray], float, Lis
|
||||
return frames, generation_time, stage_names, stage_execution_times
|
||||
|
||||
|
||||
def prepare_sampling_params(video_request: VideoGenerationRequest, default_params: Any) -> Any:
|
||||
params = deepcopy(default_params)
|
||||
params.prompt = video_request.prompt
|
||||
|
||||
if video_request.use_negative_prompt:
|
||||
params.negative_prompt = video_request.negative_prompt
|
||||
def prepare_generation_request(video_request: VideoGenerationRequest, image_path: Optional[str] = None) -> Any:
|
||||
seed = (video_request.seed if not video_request.randomize_seed
|
||||
else torch.randint(0, SEED_RANGE_MAX, (1,)).item())
|
||||
|
||||
params.seed = (video_request.seed if not video_request.randomize_seed
|
||||
else torch.randint(0, SEED_RANGE_MAX, (1,)).item())
|
||||
params.randomize_seed = video_request.randomize_seed
|
||||
params.guidance_scale = video_request.guidance_scale
|
||||
params.num_frames = video_request.num_frames
|
||||
params.height = video_request.height
|
||||
params.width = video_request.width
|
||||
params.save_video = False
|
||||
params.return_frames = True
|
||||
|
||||
return params
|
||||
# "" explicitly clears the model preset's negative prompt (None would
|
||||
# inherit it, changing this demo's long-standing behavior).
|
||||
negative_prompt = video_request.negative_prompt if video_request.use_negative_prompt else ""
|
||||
|
||||
request = GenerationRequest(
|
||||
prompt=video_request.prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
sampling=SamplingConfig(
|
||||
seed=seed,
|
||||
guidance_scale=video_request.guidance_scale,
|
||||
num_frames=video_request.num_frames,
|
||||
height=video_request.height,
|
||||
width=video_request.width,
|
||||
),
|
||||
output=OutputConfig(save_video=False, return_frames=True),
|
||||
)
|
||||
|
||||
return request, seed
|
||||
|
||||
|
||||
class BaseModelDeployment:
|
||||
@@ -185,31 +200,34 @@ class BaseModelDeployment:
|
||||
|
||||
def _initialize_generator(self, config: Dict[str, Any]) -> None:
|
||||
from fastvideo.entrypoints.video_generator import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
|
||||
print(f"Initializing model: {self.model_path}")
|
||||
self.generator = VideoGenerator.from_pretrained(
|
||||
model_path=self.model_path,
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
text_encoder_cpu_offload=config["text_encoder_cpu_offload"],
|
||||
dmd_denoising_steps=[1000, 850, 700, 550, 350, 275, 200, 125], # TODO: hardocde for I2V
|
||||
dit_precision="fp32", # TODO: hardocde for I2V
|
||||
dit_cpu_offload=config["dit_cpu_offload"],
|
||||
vae_cpu_offload=config["vae_cpu_offload"],
|
||||
VSA_sparsity=config["VSA_sparsity"],
|
||||
enable_stage_verification=False,
|
||||
self.generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=self.model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
enable_stage_verification=False,
|
||||
offload=OffloadConfig(
|
||||
text_encoder=config["text_encoder_cpu_offload"],
|
||||
dit=config["dit_cpu_offload"],
|
||||
vae=config["vae_cpu_offload"],
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
# I2V knobs without first-class typed fields yet.
|
||||
experimental={
|
||||
"dmd_denoising_steps": [1000, 850, 700, 550, 350, 275, 200, 125],
|
||||
"dit_precision": "fp32",
|
||||
"VSA_sparsity": config["VSA_sparsity"],
|
||||
},
|
||||
),
|
||||
)
|
||||
)
|
||||
self.default_params = SamplingParam.from_pretrained(self.model_path)
|
||||
self.default_params.seed = 1000
|
||||
self.default_params.num_frames = 73
|
||||
self.default_params.width = 832
|
||||
self.default_params.height = 480
|
||||
|
||||
def generate_video(self, video_request: VideoGenerationRequest) -> VideoGenerationResponse:
|
||||
total_start_time = time.time()
|
||||
|
||||
params = prepare_sampling_params(video_request, self.default_params)
|
||||
|
||||
# Save image if provided (for I2V)
|
||||
image_path = None
|
||||
@@ -218,19 +236,15 @@ class BaseModelDeployment:
|
||||
if image_path is None:
|
||||
return VideoGenerationResponse(
|
||||
video_data=None,
|
||||
seed=params.seed,
|
||||
seed=video_request.seed,
|
||||
success=False,
|
||||
error_message="Failed to save input image",
|
||||
)
|
||||
|
||||
request, seed = prepare_generation_request(video_request, image_path)
|
||||
|
||||
inference_start_time = time.time()
|
||||
result = self.generator.generate_video(
|
||||
prompt=video_request.prompt,
|
||||
sampling_param=params,
|
||||
image_path=image_path,
|
||||
save_video=False,
|
||||
return_frames=True,
|
||||
)
|
||||
result = self.generator.generate(request)
|
||||
inference_time = time.time() - inference_start_time
|
||||
|
||||
frames, generation_time, stage_names, stage_execution_times = process_generation_result(result)
|
||||
@@ -250,7 +264,7 @@ class BaseModelDeployment:
|
||||
|
||||
return VideoGenerationResponse(
|
||||
video_data=video_data,
|
||||
seed=params.seed,
|
||||
seed=seed,
|
||||
success=True,
|
||||
generation_time=generation_time,
|
||||
inference_time=inference_time,
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "./lora_out"
|
||||
def main():
|
||||
# Initialize VideoGenerator with the Wan model
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
num_gpus=1,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
lora_path="benjamin-paine/steamboat-willie-1.3b",
|
||||
lora_nickname="steamboat"
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="benjamin-paine/steamboat-willie-1.3b",
|
||||
),
|
||||
experimental={"lora_nickname": "steamboat"},
|
||||
),
|
||||
)
|
||||
)
|
||||
kwargs = {
|
||||
"height": 480,
|
||||
@@ -26,25 +39,32 @@ def main():
|
||||
prompt = "steamboat willie style, golden era animation, close-up of a short fluffy monster kneeling beside a melting red candle. the mood is one of wonder and curiosity, as the monster gazes at the flame with wide eyes and open mouth. Its pose and expression convey a sense of innocence and playfulness, as if it is exploring the world around it for the first time. The use of warm colors and dramatic lighting further enhances the cozy atmosphere of the image."
|
||||
negative_prompt = "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
|
||||
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
# sampling_param=sampling_param,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
negative_prompt=negative_prompt,
|
||||
**kwargs
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
sampling=SamplingConfig(**kwargs),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
generator.set_lora_adapter(lora_nickname="flat_color", lora_path="motimalu/wan-flat-color-1.3b-v2")
|
||||
prompt = "flat color, no lineart, blending, negative space, artist:[john kafka|ponsuke kaikai|hara id 21|yoneyama mai|fuzichoco], 1girl, sakura miko, pink hair, cowboy shot, white shirt, floral print, off shoulder, outdoors, cherry blossom, tree shade, wariza, looking up, falling petals, half-closed eyes, white sky, clouds, live2d animation, upper body, high quality cinematic video of a woman sitting under a sakura tree. Dreamy and lonely, the camera close-ups on the face of the woman as she turns towards the viewer. The Camera is steady, This is a cowboy shot. The animation is smooth and fluid."
|
||||
negative_prompt = "bad quality video,色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
negative_prompt=negative_prompt,
|
||||
**kwargs
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
sampling=SamplingConfig(**kwargs),
|
||||
output=OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -2,45 +2,60 @@
|
||||
Inference using a LoRA checkpoint from FastVideo trainer.
|
||||
"""
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest,
|
||||
GeneratorConfig, OffloadConfig, OutputConfig,
|
||||
PipelineSelection, SamplingConfig)
|
||||
|
||||
OUTPUT_PATH = "./lora_out"
|
||||
def main():
|
||||
# Initialize VideoGenerator with the Wan model
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
num_gpus=1,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
lora_path="checkpoints/wan_t2v_finetune_lora/checkpoint-160/transformer",
|
||||
lora_nickname="crush_smol"
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
lora_path="checkpoints/wan_t2v_finetune_lora/checkpoint-160/transformer",
|
||||
),
|
||||
experimental={"lora_nickname": "crush_smol"},
|
||||
),
|
||||
))
|
||||
generator.unmerge_lora_weights()
|
||||
kwargs = {
|
||||
"height": 480,
|
||||
"width": 832,
|
||||
"num_frames": 77,
|
||||
"guidance_scale": 6.0,
|
||||
"num_inference_steps": 50,
|
||||
"seed": 42,
|
||||
}
|
||||
sampling = SamplingConfig(
|
||||
height=480,
|
||||
width=832,
|
||||
num_frames=77,
|
||||
guidance_scale=6.0,
|
||||
num_inference_steps=50,
|
||||
seed=42,
|
||||
)
|
||||
output = OutputConfig(
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
)
|
||||
# Generate video with LoRA style
|
||||
prompt = "A large metal cylinder is seen pressing down on a pile of Oreo cookies, flattening them as if they were under a hydraulic press."
|
||||
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
**kwargs
|
||||
)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=sampling,
|
||||
output=output,
|
||||
))
|
||||
prompt = "A large metal cylinder is seen compressing colorful clay into a compact shape, demonstrating the power of a hydraulic press."
|
||||
video = generator.generate_video(
|
||||
prompt,
|
||||
output_path=OUTPUT_PATH,
|
||||
save_video=True,
|
||||
**kwargs
|
||||
)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=sampling,
|
||||
output=output,
|
||||
))
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -37,6 +37,17 @@ import imageio
|
||||
import torch
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig,
|
||||
ComponentConfig,
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.configs.pipelines.base import PipelineConfig
|
||||
from fastvideo.layers.quantization.nvfp4_qat_config import NVFP4QATConfig
|
||||
|
||||
@@ -148,35 +159,49 @@ def build_generator(args: argparse.Namespace) -> VideoGenerator:
|
||||
|
||||
compile_enabled = not args.no_compile
|
||||
|
||||
extra_kwargs = {}
|
||||
# ``pipeline_config`` is a PipelineConfig object (not a string path) and
|
||||
# ``output_type`` has no first-class typed field, so both are routed through
|
||||
# the pipeline experimental escape hatch.
|
||||
experimental = {"pipeline_config": pipeline_config}
|
||||
|
||||
components = ComponentConfig()
|
||||
if args.distilled_model:
|
||||
weights_path = resolve_distilled_weights(args.distilled_model)
|
||||
print(f"Using distilled weights: {args.distilled_model} -> {weights_path}")
|
||||
extra_kwargs["init_weights_from_safetensors"] = weights_path
|
||||
components.transformer_weights = weights_path
|
||||
|
||||
if args.taehv:
|
||||
# Skip the in-pipeline VAE decode entirely: the pipeline returns raw
|
||||
# latents, the Wan VAE is offloaded to CPU (and not compiled) since we
|
||||
# decode with TAEHV in this script instead.
|
||||
extra_kwargs["output_type"] = "latent"
|
||||
experimental["output_type"] = "latent"
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_id,
|
||||
pipeline_config=pipeline_config,
|
||||
num_gpus=args.num_gpus,
|
||||
# Keep everything resident on the GPU -- no offloading, except the
|
||||
# unused Wan VAE when TAEHV handles decoding.
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
dit_layerwise_offload=False,
|
||||
vae_cpu_offload=args.taehv,
|
||||
text_encoder_cpu_offload=False,
|
||||
pin_cpu_memory=False,
|
||||
enable_torch_compile=compile_enabled,
|
||||
enable_torch_compile_text_encoder=compile_enabled,
|
||||
enable_torch_compile_vae=compile_enabled and not args.taehv,
|
||||
**extra_kwargs,
|
||||
generator_config = GeneratorConfig(
|
||||
model_path=model_id,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
# Keep everything resident on the GPU -- no offloading, except the
|
||||
# unused Wan VAE when TAEHV handles decoding.
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
dit_layerwise=False,
|
||||
vae=args.taehv,
|
||||
text_encoder=False,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
compile=CompileConfig(
|
||||
enabled=compile_enabled,
|
||||
text_encoder_enabled=compile_enabled,
|
||||
vae_enabled=compile_enabled and not args.taehv,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=components,
|
||||
experimental=experimental,
|
||||
),
|
||||
)
|
||||
generator = VideoGenerator.from_config(generator_config)
|
||||
return generator
|
||||
|
||||
|
||||
@@ -237,11 +262,11 @@ def main() -> None:
|
||||
# runs below measure steady-state latency only.
|
||||
with silence_request_log():
|
||||
for _ in range(args.warmups):
|
||||
warm = generator.generate(request={
|
||||
"prompt": PROMPT,
|
||||
"sampling": {"num_inference_steps": 2, "guidance_scale": args.guidance_scale},
|
||||
"output": {"save_video": False, "return_frames": args.taehv},
|
||||
})
|
||||
warm = generator.generate(GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
sampling=SamplingConfig(num_inference_steps=2, guidance_scale=args.guidance_scale),
|
||||
output=OutputConfig(save_video=False, return_frames=args.taehv),
|
||||
))
|
||||
if args.taehv:
|
||||
taehv.decode(warm.samples)
|
||||
|
||||
@@ -257,18 +282,18 @@ def main() -> None:
|
||||
frames = None
|
||||
with silence_request_log():
|
||||
for i in range(args.benchmark_runs):
|
||||
result = generator.generate(request={
|
||||
"prompt": PROMPT,
|
||||
"sampling": {
|
||||
"num_inference_steps": args.infer_steps,
|
||||
"guidance_scale": args.guidance_scale,
|
||||
},
|
||||
"output": {
|
||||
"save_video": False,
|
||||
"return_frames": args.taehv,
|
||||
"output_path": output_path,
|
||||
},
|
||||
})
|
||||
result = generator.generate(GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
sampling=SamplingConfig(
|
||||
num_inference_steps=args.infer_steps,
|
||||
guidance_scale=args.guidance_scale,
|
||||
),
|
||||
output=OutputConfig(
|
||||
save_video=False,
|
||||
return_frames=args.taehv,
|
||||
output_path=output_path,
|
||||
),
|
||||
))
|
||||
denoise_elapsed = result.generation_time
|
||||
denoise_times.append(denoise_elapsed)
|
||||
|
||||
|
||||
@@ -2,31 +2,41 @@ import os
|
||||
import time
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
EngineConfig, GenerationRequest, GeneratorConfig, OffloadConfig,
|
||||
OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
def main():
|
||||
# set the attention backend
|
||||
# set the attention backend
|
||||
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "FLASH_ATTN"
|
||||
|
||||
start_time = time.perf_counter()
|
||||
gen = VideoGenerator.from_pretrained(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
num_gpus=1,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
)
|
||||
gen = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path="Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
vae=False,
|
||||
text_encoder=True,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
),
|
||||
),
|
||||
))
|
||||
load_time = time.perf_counter() - start_time
|
||||
print(f"Model loading time: {load_time:.2f} seconds")
|
||||
|
||||
gen_start_time = time.perf_counter()
|
||||
|
||||
gen.generate_video(
|
||||
prompt=
|
||||
"Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting.",
|
||||
seed=1024,
|
||||
output_path="example_outputs/")
|
||||
|
||||
gen.generate(
|
||||
GenerationRequest(
|
||||
prompt=
|
||||
"Will Smith casually eats noodles, his relaxed demeanor contrasting with the energetic background of a bustling street food market. The scene captures a mix of humor and authenticity. Mid-shot framing, vibrant lighting.",
|
||||
sampling=SamplingConfig(seed=1024),
|
||||
output=OutputConfig(output_path="example_outputs/")))
|
||||
|
||||
generation_time = time.perf_counter() - gen_start_time
|
||||
print(f"Video generation time: {generation_time:.2f} seconds")
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ import os
|
||||
import time
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
OUTPUT_PATH = "video_samples"
|
||||
|
||||
@@ -39,17 +43,24 @@ def main():
|
||||
mode += "_compile"
|
||||
print(f"Mode: {mode.upper()}")
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
args.model,
|
||||
num_gpus=args.num_gpus,
|
||||
nvfp4_fa4=args.nvfp4_fa4,
|
||||
use_fsdp_inference=not args.nvfp4_fa4,
|
||||
dit_cpu_offload=False,
|
||||
dit_layerwise_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
enable_torch_compile=args.compile,
|
||||
)
|
||||
if args.nvfp4_fa4:
|
||||
os.environ["FASTVIDEO_NVFP4_FA4"] = "1"
|
||||
os.environ.setdefault("CUTE_DSL_ENABLE_TVM_FFI", "1")
|
||||
|
||||
generator = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=args.model,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=not args.nvfp4_fa4,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
dit_layerwise=False,
|
||||
vae=True,
|
||||
text_encoder=True,
|
||||
),
|
||||
compile=CompileConfig(enabled=args.compile),
|
||||
),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
@@ -59,16 +70,22 @@ def main():
|
||||
|
||||
n_warmup = 2 if args.compile else 1
|
||||
for i in range(n_warmup):
|
||||
generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 2},
|
||||
"output": {"save_video": False}})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=2),
|
||||
output=OutputConfig(save_video=False),
|
||||
))
|
||||
|
||||
os.makedirs(OUTPUT_PATH, exist_ok=True)
|
||||
start = time.time()
|
||||
generator.generate(request={
|
||||
"prompt": prompt,
|
||||
"sampling": {"num_inference_steps": args.infer_steps},
|
||||
"output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")},
|
||||
})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=args.infer_steps),
|
||||
output=OutputConfig(
|
||||
save_video=True,
|
||||
output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4"),
|
||||
),
|
||||
))
|
||||
elapsed = time.time() - start
|
||||
print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s "
|
||||
f"({args.infer_steps / elapsed:.2f} it/s)")
|
||||
|
||||
@@ -69,6 +69,16 @@ def main():
|
||||
os.environ.setdefault("FASTVIDEO_ATTENTION_BACKEND", "SAGE_ATTN")
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig,
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OffloadConfig,
|
||||
OutputConfig,
|
||||
PipelineSelection,
|
||||
SamplingConfig,
|
||||
)
|
||||
from fastvideo.layers.quantization import get_quantization_config
|
||||
|
||||
mode = "bf16" if args.bf16 else f"fp8_{args.granularity}"
|
||||
@@ -79,25 +89,33 @@ def main():
|
||||
|
||||
taehv_model = load_taehv(args.taehv_checkpoint) if use_taehv else None
|
||||
|
||||
# transformer_quant needs a QuantizationConfig *instance* — the bare string
|
||||
# is not resolved on the from_pretrained kwarg path.
|
||||
extra = {} if args.bf16 else {
|
||||
"transformer_quant": get_quantization_config("FP8")(granularity=args.granularity)
|
||||
}
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
args.model,
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=False,
|
||||
dit_cpu_offload=False,
|
||||
dit_layerwise_offload=False,
|
||||
vae_cpu_offload=use_taehv,
|
||||
text_encoder_cpu_offload=False,
|
||||
pin_cpu_memory=False,
|
||||
enable_torch_compile=not args.no_compile,
|
||||
enable_torch_compile_vae=not args.no_compile and not use_taehv,
|
||||
output_type="latent" if use_taehv else "pil",
|
||||
**extra,
|
||||
)
|
||||
# ``output_type`` and ``transformer_quant`` have no first-class typed
|
||||
# fields yet, so they ride the pipeline.experimental escape hatch (same
|
||||
# place the legacy from_pretrained shim routed them). The typed
|
||||
# QuantizationConfig only accepts a quant-name string, so it can't carry
|
||||
# FP8's ``granularity`` arg — pass the resolved config instance instead.
|
||||
experimental = {"output_type": "latent" if use_taehv else "pil"}
|
||||
if not args.bf16:
|
||||
experimental["transformer_quant"] = get_quantization_config("FP8")(granularity=args.granularity)
|
||||
generator = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=args.model,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=False,
|
||||
offload=OffloadConfig(
|
||||
dit=False,
|
||||
dit_layerwise=False,
|
||||
vae=use_taehv,
|
||||
text_encoder=False,
|
||||
pin_cpu_memory=False,
|
||||
),
|
||||
compile=CompileConfig(
|
||||
enabled=not args.no_compile,
|
||||
vae_enabled=not args.no_compile and not use_taehv,
|
||||
),
|
||||
),
|
||||
pipeline=PipelineSelection(experimental=experimental),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
@@ -107,28 +125,34 @@ def main():
|
||||
|
||||
n_warmup = 1 if not args.no_compile else 0
|
||||
for _ in range(n_warmup):
|
||||
generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 3, "guidance_scale": 1.0},
|
||||
"output": {"save_video": False}})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=3, guidance_scale=1.0),
|
||||
output=OutputConfig(save_video=False),
|
||||
))
|
||||
|
||||
os.makedirs(OUTPUT_PATH, exist_ok=True)
|
||||
start = time.time()
|
||||
if use_taehv:
|
||||
result = generator.generate(request={
|
||||
"prompt": prompt,
|
||||
"sampling": {"num_inference_steps": args.infer_steps, "guidance_scale": 1.0},
|
||||
"output": {"save_video": False},
|
||||
})
|
||||
result = generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=args.infer_steps, guidance_scale=1.0),
|
||||
output=OutputConfig(save_video=False),
|
||||
))
|
||||
import imageio
|
||||
frames = decode_with_taehv(taehv_model, result.samples)
|
||||
video_path = os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")
|
||||
imageio.mimsave(video_path, frames, fps=16, format="mp4")
|
||||
print(f"Saved TAEHV-decoded video to: {video_path}")
|
||||
else:
|
||||
generator.generate(request={
|
||||
"prompt": prompt,
|
||||
"sampling": {"num_inference_steps": args.infer_steps, "guidance_scale": 1.0},
|
||||
"output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")},
|
||||
})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=args.infer_steps, guidance_scale=1.0),
|
||||
output=OutputConfig(
|
||||
save_video=True,
|
||||
output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4"),
|
||||
),
|
||||
))
|
||||
elapsed = time.time() - start
|
||||
print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s "
|
||||
f"({args.infer_steps / elapsed:.2f} it/s)")
|
||||
|
||||
@@ -45,26 +45,29 @@ def main():
|
||||
|
||||
# Import after the env var so the platform picks up the selection.
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.layers.quantization import get_quantization_config
|
||||
from fastvideo.api import (
|
||||
CompileConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, QuantizationConfig, SamplingConfig,
|
||||
)
|
||||
|
||||
mode = "bf16" if args.bf16 else args.quant_method
|
||||
if args.compile:
|
||||
mode += "_compile"
|
||||
print(f"Mode: {mode.upper()}")
|
||||
|
||||
# transformer_quant needs a QuantizationConfig *instance* — the bare string
|
||||
# is not resolved on the from_pretrained kwarg path.
|
||||
extra = {} if args.bf16 else {"transformer_quant": get_quantization_config(args.quant_method)()}
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
args.model,
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=args.bf16,
|
||||
dit_cpu_offload=False,
|
||||
vae_cpu_offload=True,
|
||||
text_encoder_cpu_offload=True,
|
||||
enable_torch_compile=args.compile,
|
||||
**extra,
|
||||
)
|
||||
# transformer_quant takes the config name (string); the typed path resolves
|
||||
# the QuantizationConfig class.
|
||||
quantization = None if args.bf16 else QuantizationConfig(transformer_quant=args.quant_method)
|
||||
generator = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=args.model,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
use_fsdp_inference=args.bf16,
|
||||
offload=OffloadConfig(dit=False, vae=True, text_encoder=True),
|
||||
compile=CompileConfig(enabled=args.compile),
|
||||
quantization=quantization,
|
||||
),
|
||||
))
|
||||
|
||||
prompt = (
|
||||
"A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes "
|
||||
@@ -74,16 +77,20 @@ def main():
|
||||
|
||||
n_warmup = 2 if args.compile else 1
|
||||
for _ in range(n_warmup):
|
||||
generator.generate(request={"prompt": prompt, "sampling": {"num_inference_steps": 2},
|
||||
"output": {"save_video": False}})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=2),
|
||||
output=OutputConfig(save_video=False),
|
||||
))
|
||||
|
||||
os.makedirs(OUTPUT_PATH, exist_ok=True)
|
||||
start = time.time()
|
||||
generator.generate(request={
|
||||
"prompt": prompt,
|
||||
"sampling": {"num_inference_steps": args.infer_steps},
|
||||
"output": {"save_video": True, "output_path": os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")},
|
||||
})
|
||||
generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(num_inference_steps=args.infer_steps),
|
||||
output=OutputConfig(save_video=True,
|
||||
output_path=os.path.join(OUTPUT_PATH, f"raccoon_{mode}.mp4")),
|
||||
))
|
||||
elapsed = time.time() - start
|
||||
print(f"[{mode.upper()}] {args.infer_steps} steps in {elapsed:.2f}s "
|
||||
f"({args.infer_steps / elapsed:.2f} it/s)")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (ComponentConfig, EngineConfig, GenerationRequest,
|
||||
GeneratorConfig, InputConfig, OffloadConfig,
|
||||
OutputConfig, PipelineSelection, QuantizationConfig)
|
||||
import argparse
|
||||
|
||||
OUTPUT_PATH = "video_samples_wan2_2_5B_ti2v"
|
||||
@@ -10,28 +13,39 @@ def main(text_encoder_path: str):
|
||||
# If a local path is provided, FastVideo will make a best effort
|
||||
# attempt to identify the optimal arguments.
|
||||
model_name = "Wan-AI/Wan2.2-TI2V-5B-Diffusers"
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
model_name,
|
||||
# FastVideo will automatically handle distributed setup
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
dit_cpu_offload=True,
|
||||
vae_cpu_offload=False,
|
||||
text_encoder_cpu_offload=False,
|
||||
# AbsMaxFP8 is the quantization method used by ComfyUI;
|
||||
# check fastvideo/layers/quantization/* for more quantization methods
|
||||
override_text_encoder_quant="AbsMaxFP8",
|
||||
# for Wan 2.2, this is the path to "umt5_xxl_fp8_e4m3fn_scaled.safetensors"
|
||||
override_text_encoder_safetensors=text_encoder_path,
|
||||
pin_cpu_memory=True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
)
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=model_name,
|
||||
# FastVideo will automatically handle distributed setup
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
use_fsdp_inference=True,
|
||||
offload=OffloadConfig(
|
||||
dit=True,
|
||||
vae=False,
|
||||
text_encoder=False,
|
||||
pin_cpu_memory=
|
||||
True, # set to false if low CPU RAM or hit obscure "CUDA error: Invalid argument"
|
||||
),
|
||||
# AbsMaxFP8 is the quantization method used by ComfyUI;
|
||||
# check fastvideo/layers/quantization/* for more quantization methods
|
||||
quantization=QuantizationConfig(text_encoder_quant="AbsMaxFP8"),
|
||||
),
|
||||
pipeline=PipelineSelection(
|
||||
components=ComponentConfig(
|
||||
# for Wan 2.2, this is the path to "umt5_xxl_fp8_e4m3fn_scaled.safetensors"
|
||||
text_encoder_weights=text_encoder_path, ), ),
|
||||
))
|
||||
|
||||
# I2V is triggered just by passing in an image_path argument
|
||||
prompt = "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside."
|
||||
image_path = "https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG"
|
||||
video = generator.generate_video(
|
||||
prompt, output_path=OUTPUT_PATH, save_video=True, image_path=image_path
|
||||
)
|
||||
video = generator.generate(
|
||||
GenerationRequest(
|
||||
prompt=prompt,
|
||||
inputs=InputConfig(image_path=image_path),
|
||||
output=OutputConfig(output_path=OUTPUT_PATH, save_video=True),
|
||||
))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -22,6 +22,14 @@ import os
|
||||
import time
|
||||
|
||||
from fastvideo import VideoGenerator
|
||||
from fastvideo.api import (
|
||||
CompileConfig,
|
||||
EngineConfig,
|
||||
GenerationRequest,
|
||||
GeneratorConfig,
|
||||
OutputConfig,
|
||||
SamplingConfig,
|
||||
)
|
||||
|
||||
PROMPT = (
|
||||
"A high-definition video of a robotic arm welding a metal structure, "
|
||||
@@ -42,24 +50,27 @@ def main() -> None:
|
||||
|
||||
os.makedirs("video_samples", exist_ok=True)
|
||||
|
||||
generator = VideoGenerator.from_pretrained(
|
||||
args.model,
|
||||
num_gpus=args.num_gpus,
|
||||
enable_torch_compile=args.compile,
|
||||
generator = VideoGenerator.from_config(
|
||||
GeneratorConfig(
|
||||
model_path=args.model,
|
||||
engine=EngineConfig(
|
||||
num_gpus=args.num_gpus,
|
||||
compile=CompileConfig(enabled=args.compile),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
def _run(tag: str) -> float:
|
||||
save = tag == "measured"
|
||||
# Modern typed-request API (generate_video is deprecated). Same
|
||||
# prompt/seed/shapes both runs so the compiled graph is reused.
|
||||
request: dict = {
|
||||
"prompt": PROMPT,
|
||||
"sampling": {"seed": 1024},
|
||||
"output": {"save_video": save},
|
||||
}
|
||||
# Same prompt/seed/shapes both runs so the compiled graph is reused.
|
||||
output = OutputConfig(save_video=save)
|
||||
if save:
|
||||
request["output"]["output_path"] = (
|
||||
f"video_samples/torch_compile_{tag}.mp4")
|
||||
output.output_path = f"video_samples/torch_compile_{tag}.mp4"
|
||||
request = GenerationRequest(
|
||||
prompt=PROMPT,
|
||||
sampling=SamplingConfig(seed=1024),
|
||||
output=output,
|
||||
)
|
||||
t0 = time.perf_counter()
|
||||
generator.generate(request)
|
||||
return time.perf_counter() - t0
|
||||
|
||||
+22
-3
@@ -37,15 +37,34 @@ def parse_config(config_type: type[T], raw: Mapping[str, Any] | T) -> T:
|
||||
if not isinstance(raw, Mapping):
|
||||
raise ConfigValidationError("", f"expected mapping for {config_type.__name__}")
|
||||
parsed = _SchemaParser().parse_dataclass(config_type, raw, "")
|
||||
# None is the schema-wide sentinel for "not specified, inherit the
|
||||
# model preset", so an explicit YAML/JSON null must not bind as an
|
||||
# explicit path — otherwise it would stomp preset values with None.
|
||||
if config_type is GenerationRequest:
|
||||
return bind_generation_request_raw(parsed, raw)
|
||||
return bind_generation_request_raw(parsed, drop_none_leaves(raw))
|
||||
if config_type is RunConfig:
|
||||
return bind_run_config_raw(parsed, raw)
|
||||
return bind_run_config_raw(parsed, drop_none_leaves(raw))
|
||||
if config_type is ServeConfig:
|
||||
return bind_serve_config_raw(parsed, raw)
|
||||
return bind_serve_config_raw(parsed, drop_none_leaves(raw))
|
||||
return parsed
|
||||
|
||||
|
||||
def drop_none_leaves(raw: Any) -> Any:
|
||||
"""Prune None leaves — and dicts emptied by the pruning — from a raw
|
||||
config mapping, so they are never recorded as explicit paths."""
|
||||
if not isinstance(raw, dict):
|
||||
return raw
|
||||
pruned: dict[str, Any] = {}
|
||||
for key, value in raw.items():
|
||||
if value is None:
|
||||
continue
|
||||
value = drop_none_leaves(value)
|
||||
if isinstance(value, dict) and not value:
|
||||
continue
|
||||
pruned[key] = value
|
||||
return pruned
|
||||
|
||||
|
||||
def config_to_dict(config: Any) -> Any:
|
||||
"""Serialize a typed config object into plain Python containers."""
|
||||
if dataclasses.is_dataclass(config) and not isinstance(config, type):
|
||||
|
||||
+27
-16
@@ -136,19 +136,27 @@ class InputConfig:
|
||||
|
||||
@dataclass
|
||||
class SamplingConfig:
|
||||
num_videos_per_prompt: int = 1
|
||||
seed: int = 1024
|
||||
num_frames: int = 125
|
||||
height: int = 720
|
||||
width: int = 1280
|
||||
height_sr: int = 1072
|
||||
width_sr: int = 1920
|
||||
fps: int = 24
|
||||
num_inference_steps: int = 50
|
||||
num_inference_steps_sr: int = 50
|
||||
guidance_scale: float = 1.0
|
||||
"""Sampling knobs for a generation request.
|
||||
|
||||
``None`` means "not specified": the value is inherited from the
|
||||
model's preset (``SamplingParam.from_pretrained``) at generate time.
|
||||
Set a field to override the preset. There is deliberately no way to
|
||||
express "explicitly the schema default" — pass the concrete value
|
||||
you want instead.
|
||||
"""
|
||||
num_videos_per_prompt: int | None = None
|
||||
seed: int | None = None
|
||||
num_frames: int | None = None
|
||||
height: int | None = None
|
||||
width: int | None = None
|
||||
height_sr: int | None = None
|
||||
width_sr: int | None = None
|
||||
fps: int | None = None
|
||||
num_inference_steps: int | None = None
|
||||
num_inference_steps_sr: int | None = None
|
||||
guidance_scale: float | None = None
|
||||
guidance_scale_2: float | None = None
|
||||
guidance_rescale: float = 0.0
|
||||
guidance_rescale: float | None = None
|
||||
true_cfg_scale: float | None = None
|
||||
boundary_ratio: float | None = None
|
||||
sigmas: list[float] | None = None
|
||||
@@ -194,6 +202,8 @@ class GenerationPlan:
|
||||
class GenerationRequest:
|
||||
prompt: str | list[str] | None = None
|
||||
negative_prompt: str | None = None
|
||||
"""``None`` inherits the model preset's negative prompt; pass ``""``
|
||||
to explicitly clear it."""
|
||||
inputs: InputConfig = field(default_factory=InputConfig)
|
||||
sampling: SamplingConfig = field(default_factory=SamplingConfig)
|
||||
runtime: RequestRuntimeConfig = field(default_factory=RequestRuntimeConfig)
|
||||
@@ -261,15 +271,16 @@ class ServeConfig:
|
||||
the incoming body as the operator-pinned baseline.
|
||||
|
||||
Important nuance: only fields the operator **explicitly wrote** in the
|
||||
serve YAML/JSON count as defaults. Although the in-memory object is
|
||||
fully populated (schema defaults fill every unset field), the merge
|
||||
walks ``_fastvideo_explicit_paths`` — populated during parse — so
|
||||
serve YAML/JSON count as defaults. Unset sampling fields stay ``None``
|
||||
("inherit the model preset"), other sections keep their schema
|
||||
defaults in memory — but the merge walks ``_fastvideo_explicit_paths``
|
||||
(populated during parse; explicit ``null`` is treated as unset), so
|
||||
unset fields are *not* forced onto requests. Per-request precedence:
|
||||
|
||||
body (client-explicit) > default_request (operator-explicit)
|
||||
> hardcoded fallback (e.g. ``fps=24``)
|
||||
|
||||
See :func:`fastvideo.api.compat.explicit_request_updates` for the
|
||||
See :func:`fastvideo.api.translation.explicit_request_updates` for the
|
||||
projection and ``entrypoints/openai/video_api.py::_build_generation_kwargs``
|
||||
for the merge.
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Translation layer between the public typed API and the legacy internals.
|
||||
|
||||
This module (formerly ``fastvideo/api/compat.py``) is the single bridge that
|
||||
converts typed public config objects (:class:`GeneratorConfig`,
|
||||
:class:`GenerationRequest`) into the internal :class:`FastVideoArgs` /
|
||||
:class:`SamplingParam` the runtime still consumes, plus the inbound
|
||||
normalization helpers used by every public entrypoint.
|
||||
|
||||
It is *internal* and not part of the stable public surface. The legacy
|
||||
forward-translation helpers (``legacy_from_pretrained_to_config``,
|
||||
``legacy_generate_call_to_request`` and friends) exist only to support the
|
||||
deprecated ``VideoGenerator.from_pretrained(model, **kwargs)`` /
|
||||
``generate_video(...)`` entry points. The reverse-translation
|
||||
(``generator_config_to_fastvideo_args``, ``request_to_sampling_param``) is
|
||||
slated for elimination once ``FastVideoArgs`` becomes a view over
|
||||
``GeneratorConfig`` and ``ForwardBatch`` reads ``GenerationRequest`` directly
|
||||
(see ``.agents/memory/dreamverse-integration/pr-roadmap.md``, PRs 15-17).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
@@ -8,7 +26,12 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastvideo.api.overrides import apply_overrides, normalize_overrides
|
||||
from fastvideo.api.parser import config_to_dict, load_raw_config, parse_config
|
||||
from fastvideo.api.parser import (
|
||||
config_to_dict,
|
||||
drop_none_leaves,
|
||||
load_raw_config,
|
||||
parse_config,
|
||||
)
|
||||
from fastvideo.api.request_metadata import (
|
||||
EXPLICIT_PATHS_ATTR,
|
||||
bind_generation_request_raw,
|
||||
@@ -314,8 +337,12 @@ def normalize_generation_request(request: GenerationRequest | Mapping[str, Any],
|
||||
|
||||
if not hasattr(normalized, EXPLICIT_PATHS_ATTR):
|
||||
# Request wasn't bound through the parser (e.g. constructed
|
||||
# directly). Treat every currently-set field as explicit.
|
||||
bind_generation_request_raw(normalized, _serialize_generation_request(normalized))
|
||||
# directly). Bind only non-None leaves as explicit: None is the
|
||||
# schema-wide sentinel for "not specified, inherit the model
|
||||
# preset", so treating it as explicit would stomp preset values
|
||||
# with schema defaults. (parse_config applies the same pruning
|
||||
# to YAML/JSON nulls.)
|
||||
bind_generation_request_raw(normalized, drop_none_leaves(_serialize_generation_request(normalized)))
|
||||
return normalized
|
||||
|
||||
|
||||
@@ -364,15 +391,13 @@ def request_to_sampling_param(
|
||||
updates = explicit_request_updates(request)
|
||||
|
||||
for key, value in updates.items():
|
||||
if key == "return_state":
|
||||
# Already translated to return_continuation_state above.
|
||||
continue
|
||||
if hasattr(sampling_param, key):
|
||||
setattr(sampling_param, key, deepcopy(value))
|
||||
elif key in _REQUEST_PIPELINE_OVERRIDE_FIELDS:
|
||||
continue
|
||||
elif value == _SCHEMA_DEFAULT_UPDATES.get(key, _MISSING):
|
||||
# Schema-default field that isn't on SamplingParam; tolerated
|
||||
# because direct GenerationRequest(...) construction has no
|
||||
# way to distinguish "user set" from "schema default".
|
||||
continue
|
||||
else:
|
||||
raise ValueError(f"Request field {key!r} is not supported by sampling params for {model_path}")
|
||||
|
||||
@@ -476,11 +501,13 @@ def explicit_request_updates(request: GenerationRequest) -> dict[str, Any]:
|
||||
This is what makes ``ServeConfig.default_request`` work as an
|
||||
operator-pinned baseline rather than a full override: a YAML with just
|
||||
``sampling.seed: 42`` yields ``{"seed": 42}``, not the full sampling
|
||||
config with its 15 schema defaults.
|
||||
config. (Sampling fields default to ``None`` = "inherit the model
|
||||
preset", so for directly-constructed requests only non-None leaves
|
||||
are bound as explicit.)
|
||||
|
||||
Precondition: the request must carry ``_fastvideo_explicit_paths`` —
|
||||
populated by :func:`fastvideo.api.parser.parse_config` or
|
||||
:func:`fastvideo.api.compat.normalize_generation_request`. Calling on
|
||||
:func:`fastvideo.api.translation.normalize_generation_request`. Calling on
|
||||
a raw ``GenerationRequest()`` asserts.
|
||||
"""
|
||||
assert hasattr(request,
|
||||
@@ -579,8 +606,6 @@ def _serialize_generation_request(request: GenerationRequest) -> dict[str, Any]:
|
||||
return deepcopy(config_to_dict(request))
|
||||
|
||||
|
||||
_SCHEMA_DEFAULT_UPDATES = _extract_request_updates(config_to_dict(GenerationRequest()))
|
||||
|
||||
_KNOWN_CONTINUATION_KINDS: set[str] = set()
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import argparse
|
||||
import os
|
||||
from typing import cast
|
||||
|
||||
from fastvideo.api.compat import generator_config_to_fastvideo_args
|
||||
from fastvideo.api.translation import generator_config_to_fastvideo_args
|
||||
from fastvideo.entrypoints.cli.cli_types import CLISubcommand
|
||||
from fastvideo.entrypoints.cli.inference_config import build_serve_config
|
||||
from fastvideo.logger import init_logger
|
||||
|
||||
@@ -19,7 +19,7 @@ from fastapi import (
|
||||
)
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from fastvideo.api.compat import explicit_request_updates
|
||||
from fastvideo.api.translation import explicit_request_updates
|
||||
from fastvideo.api.schema import GenerationRequest
|
||||
from fastvideo.entrypoints.openai.state import (
|
||||
get_default_request,
|
||||
@@ -60,12 +60,13 @@ def _build_generation_kwargs(
|
||||
defaults on the dataclass are *not* treated as defaults here.
|
||||
3. Hardcoded fallback (e.g. ``fps=24`` when neither side set it).
|
||||
|
||||
Why gate on ``model_fields_set`` / explicit paths? Both the request
|
||||
Pydantic model and the ``GenerationRequest`` dataclass carry schema
|
||||
defaults (e.g. ``seed=1024``, ``num_frames=125``). Without the gate
|
||||
those would masquerade as intent and shadow the other side — the
|
||||
gate preserves "operator pinned it" vs. "dataclass happened to have
|
||||
that default."
|
||||
Why gate on ``model_fields_set`` / explicit paths? The request
|
||||
Pydantic model carries schema defaults, and the ``GenerationRequest``
|
||||
dataclass carries non-None defaults outside ``sampling`` (e.g.
|
||||
``output_path``; sampling fields default to None = "inherit the
|
||||
model preset"). Without the gate those would masquerade as intent
|
||||
and shadow the other side — the gate preserves "operator pinned it"
|
||||
vs. "dataclass happened to have that default."
|
||||
"""
|
||||
kwargs: dict[str, Any] = {}
|
||||
if default_request is not None:
|
||||
|
||||
@@ -48,10 +48,14 @@ class MockGenerator:
|
||||
def generate(self, request: GenerationRequest) -> dict[str, Any]:
|
||||
if self.sleep_ms:
|
||||
time.sleep(self.sleep_ms / 1000.0)
|
||||
width = max(16, request.sampling.width)
|
||||
height = max(16, request.sampling.height)
|
||||
num_frames = max(1, request.sampling.num_frames)
|
||||
frames = [_gradient_frame(height, width, idx, seed=request.sampling.seed) for idx in range(num_frames)]
|
||||
# Requests carry None for unset sampling fields (normally resolved
|
||||
# against the model preset); the mock has no model, so fall back
|
||||
# to fixed values here.
|
||||
width = max(16, request.sampling.width or 16)
|
||||
height = max(16, request.sampling.height or 16)
|
||||
num_frames = max(1, request.sampling.num_frames or 1)
|
||||
seed = request.sampling.seed if request.sampling.seed is not None else 1024
|
||||
frames = [_gradient_frame(height, width, idx, seed=seed) for idx in range(num_frames)]
|
||||
state = ContinuationState(
|
||||
kind="ltx2.v1",
|
||||
payload={
|
||||
@@ -90,6 +94,9 @@ def build_mock_app(*, sleep_ms: float = 0.0):
|
||||
width=256,
|
||||
fps=24,
|
||||
num_inference_steps=1,
|
||||
# The mock renders frames from the seed directly (no model, no
|
||||
# preset resolution), so it must be concrete here.
|
||||
seed=1024,
|
||||
)
|
||||
return build_app(serve_config, MockGenerator(sleep_ms=sleep_ms))
|
||||
|
||||
|
||||
@@ -95,6 +95,19 @@ class ServerState:
|
||||
session_store: SessionStore
|
||||
|
||||
|
||||
def _validate_default_sampling_pins(serve_config: ServeConfig) -> None:
|
||||
"""The WS protocol consumes these before any model-preset resolution
|
||||
happens (segment frames, MP4 encoder dims), so the operator must pin
|
||||
them; SamplingConfig defaults them to None = "inherit preset", which
|
||||
has no meaning at this layer."""
|
||||
required = ("num_frames", "height", "width", "fps", "num_inference_steps")
|
||||
missing = [name for name in required if getattr(serve_config.default_request.sampling, name) is None]
|
||||
if missing:
|
||||
raise ValueError("Streaming server requires explicit default_request.sampling values for: " +
|
||||
", ".join(missing) + ". Set them in the serve config YAML "
|
||||
"(e.g. `default_request: {sampling: {num_frames: 121, ...}}`).")
|
||||
|
||||
|
||||
def build_app(
|
||||
serve_config: ServeConfig,
|
||||
generator: _GeneratorProto | None = None,
|
||||
@@ -118,6 +131,8 @@ def build_app(
|
||||
if (generator is None) == (pool is None):
|
||||
raise ValueError("build_app requires exactly one of `generator` or `pool`")
|
||||
|
||||
_validate_default_sampling_pins(serve_config)
|
||||
|
||||
store = session_store or InMemorySessionStore()
|
||||
if pool is None:
|
||||
assert generator is not None
|
||||
@@ -183,6 +198,8 @@ def run_server(serve_config: ServeConfig, *, generator: _GeneratorProto | None =
|
||||
if serve_config.streaming is None:
|
||||
raise ValueError("ServeConfig.streaming must be set to launch the streaming server; "
|
||||
"got None. Add a `streaming:` block to your serve config.")
|
||||
# Fail on config mistakes before paying the multi-minute model boot.
|
||||
_validate_default_sampling_pins(serve_config)
|
||||
|
||||
import uvicorn
|
||||
|
||||
@@ -490,7 +507,9 @@ def _build_generation_request(
|
||||
}
|
||||
request = GenerationRequest(
|
||||
prompt=message.prompt,
|
||||
negative_prompt=message.negative_prompt or base.negative_prompt,
|
||||
# None inherits (base, then model preset); an explicit "" from
|
||||
# the client clears the negative prompt and must survive.
|
||||
negative_prompt=(message.negative_prompt if message.negative_prompt is not None else base.negative_prompt),
|
||||
inputs=InputConfig(image_path=session.metadata.get("session_init_image"), ),
|
||||
sampling=SamplingConfig(**sampling_kwargs),
|
||||
output=OutputConfig(save_video=False, return_frames=True, return_state=True),
|
||||
|
||||
@@ -26,7 +26,7 @@ import torch
|
||||
import torchvision
|
||||
from einops import rearrange
|
||||
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
expand_request_prompt_batch,
|
||||
generator_config_to_fastvideo_args,
|
||||
legacy_from_pretrained_to_config,
|
||||
@@ -314,7 +314,12 @@ class VideoGenerator:
|
||||
import asyncio
|
||||
|
||||
normalized = normalize_generation_request(request)
|
||||
total_steps = max(1, normalized.sampling.num_inference_steps)
|
||||
steps = normalized.sampling.num_inference_steps
|
||||
if steps is None:
|
||||
# Unset = inherit the model preset; resolve it here so the
|
||||
# advertised total matches what the run will actually do.
|
||||
steps = SamplingParam.from_pretrained(self.fastvideo_args.model_path).num_inference_steps
|
||||
total_steps = max(1, steps or 1)
|
||||
yield VideoProgressEvent(step=0, total_steps=total_steps, stage="denoise")
|
||||
|
||||
if log_queue:
|
||||
|
||||
@@ -23,7 +23,7 @@ from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from fastvideo.api.compat import register_continuation_kind
|
||||
from fastvideo.api.translation import register_continuation_kind
|
||||
from fastvideo.api.schema import ContinuationState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
@@ -5,7 +5,7 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from fastvideo.api.compat import request_to_sampling_param
|
||||
from fastvideo.api.translation import request_to_sampling_param
|
||||
from fastvideo.entrypoints.cli import main as cli_main
|
||||
from fastvideo.entrypoints.cli.generate import GenerateSubcommand
|
||||
from fastvideo.entrypoints.cli.inference_config import (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Tests for ``fastvideo.api.compat`` translation helpers covering the
|
||||
"""Tests for ``fastvideo.api.translation`` translation helpers covering the
|
||||
typed CompileConfig + PipelineSelection.vae_tiling surfaces promoted in
|
||||
PR 6.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
generator_config_to_fastvideo_args,
|
||||
legacy_from_pretrained_to_config,
|
||||
)
|
||||
|
||||
@@ -37,5 +37,6 @@ def test_serve_config_includes_server_and_default_request_defaults() -> None:
|
||||
"port": 8000,
|
||||
"output_dir": "outputs/",
|
||||
}
|
||||
assert dumped["default_request"]["sampling"]["fps"] == 24
|
||||
# Sampling fields default to None = "inherit the model preset".
|
||||
assert dumped["default_request"]["sampling"]["fps"] is None
|
||||
assert dumped["default_request"]["runtime"]["enable_teacache"] is False
|
||||
|
||||
@@ -21,7 +21,7 @@ import torch
|
||||
# Importing compat first, then the LTX-2 module, exercises the
|
||||
# self-registration side effect on import (important for the API
|
||||
# test suite where the pipeline package isn't otherwise imported).
|
||||
from fastvideo.api import compat as api_compat # noqa: F401
|
||||
from fastvideo.api import translation as api_compat # noqa: F401
|
||||
from fastvideo.api.schema import (
|
||||
ContinuationState,
|
||||
GenerationRequest,
|
||||
@@ -217,7 +217,7 @@ class TestCompatLayerWireUp:
|
||||
# PR 7 removes the NotImplementedError for request.state; build a
|
||||
# minimal GenerationRequest carrying an LTX-2 state and make sure
|
||||
# the public boundary accepts it.
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
normalize_generation_request,
|
||||
_validate_continuation_state,
|
||||
)
|
||||
@@ -230,13 +230,13 @@ class TestCompatLayerWireUp:
|
||||
_validate_continuation_state(normalized.state)
|
||||
|
||||
def test_unknown_kind_rejected_at_boundary(self):
|
||||
from fastvideo.api.compat import _validate_continuation_state
|
||||
from fastvideo.api.translation import _validate_continuation_state
|
||||
with pytest.raises(ValueError, match="Unknown ContinuationState kind"):
|
||||
_validate_continuation_state(
|
||||
ContinuationState(kind="mystery.v1", payload={}))
|
||||
|
||||
def test_empty_kind_rejected_at_boundary(self):
|
||||
from fastvideo.api.compat import _validate_continuation_state
|
||||
from fastvideo.api.translation import _validate_continuation_state
|
||||
with pytest.raises(ValueError, match="non-empty string"):
|
||||
_validate_continuation_state(
|
||||
ContinuationState(kind="", payload={}))
|
||||
|
||||
@@ -19,7 +19,7 @@ from copy import deepcopy
|
||||
|
||||
import pytest
|
||||
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
generator_config_to_fastvideo_args,
|
||||
legacy_from_pretrained_to_config,
|
||||
)
|
||||
@@ -208,7 +208,7 @@ class TestRefineFlattenCoversAllTypedFields:
|
||||
|
||||
def test_all_fields_reemitted(self, monkeypatch) -> None:
|
||||
from fastvideo import fastvideo_args as fva
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
generator_config_to_fastvideo_args,
|
||||
)
|
||||
from fastvideo.api.schema import GeneratorConfig, PipelineSelection
|
||||
|
||||
@@ -169,19 +169,21 @@ def test_load_run_config_supports_yaml_roundtrip(tmp_path) -> None:
|
||||
"stage1_video": None,
|
||||
},
|
||||
"sampling": {
|
||||
"num_videos_per_prompt": 1,
|
||||
"seed": 1024,
|
||||
# None = inherit the model preset at generate time; only
|
||||
# the YAML-set num_frames carries a value.
|
||||
"num_videos_per_prompt": None,
|
||||
"seed": None,
|
||||
"num_frames": 16,
|
||||
"height": 720,
|
||||
"width": 1280,
|
||||
"height_sr": 1072,
|
||||
"width_sr": 1920,
|
||||
"fps": 24,
|
||||
"num_inference_steps": 50,
|
||||
"num_inference_steps_sr": 50,
|
||||
"guidance_scale": 1.0,
|
||||
"height": None,
|
||||
"width": None,
|
||||
"height_sr": None,
|
||||
"width_sr": None,
|
||||
"fps": None,
|
||||
"num_inference_steps": None,
|
||||
"num_inference_steps_sr": None,
|
||||
"guidance_scale": None,
|
||||
"guidance_scale_2": None,
|
||||
"guidance_rescale": 0.0,
|
||||
"guidance_rescale": None,
|
||||
"true_cfg_scale": None,
|
||||
"boundary_ratio": None,
|
||||
"sigmas": None,
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Regression tests for None-sentinel SamplingConfig semantics.
|
||||
|
||||
A directly-constructed ``GenerationRequest`` must inherit the model
|
||||
preset (``SamplingParam.from_pretrained``) for every sampling field the
|
||||
caller did not set. Before the None-sentinel change, direct construction
|
||||
marked every schema default as explicit and stomped the preset —
|
||||
e.g. FastWan's 3 distilled steps silently became 50.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from fastvideo.api.sampling_param import SamplingParam
|
||||
from fastvideo.api.schema import GenerationRequest, SamplingConfig
|
||||
from fastvideo.api.translation import (
|
||||
normalize_generation_request,
|
||||
request_to_sampling_param,
|
||||
)
|
||||
|
||||
|
||||
def _resolve(request: GenerationRequest) -> SamplingParam:
|
||||
# Mirror the production path: generate() normalizes (binding explicit
|
||||
# paths) before translating to a SamplingParam.
|
||||
return request_to_sampling_param(normalize_generation_request(request), model_path=MODEL)
|
||||
|
||||
# Distilled preset whose values differ from every old schema default
|
||||
# (3 steps vs 50, gs 3.0 vs 1.0, 448x832 vs 720x1280, 61 frames vs 125,
|
||||
# fps 16 vs 24) — if inheritance breaks, every assertion below fails.
|
||||
MODEL = "FastVideo/FastWan2.1-T2V-1.3B-Diffusers"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def preset() -> SamplingParam:
|
||||
return SamplingParam.from_pretrained(MODEL)
|
||||
|
||||
|
||||
class TestDirectConstructionInheritsPreset:
|
||||
|
||||
def test_bare_request_preserves_preset(self, preset: SamplingParam) -> None:
|
||||
resolved = _resolve(GenerationRequest(prompt="a fox"))
|
||||
assert resolved.num_inference_steps == preset.num_inference_steps == 3
|
||||
assert resolved.guidance_scale == preset.guidance_scale == 3.0
|
||||
assert (resolved.height, resolved.width) == (preset.height, preset.width) == (448, 832)
|
||||
assert resolved.num_frames == preset.num_frames
|
||||
assert resolved.fps == preset.fps == 16
|
||||
assert resolved.negative_prompt == preset.negative_prompt
|
||||
assert resolved.negative_prompt # preset prompt is non-empty
|
||||
|
||||
def test_partial_sampling_overrides_only_set_fields(self, preset: SamplingParam) -> None:
|
||||
resolved = _resolve(GenerationRequest(prompt="a fox", sampling=SamplingConfig(num_frames=81)))
|
||||
assert resolved.num_frames == 81
|
||||
assert resolved.num_inference_steps == preset.num_inference_steps
|
||||
assert resolved.guidance_scale == preset.guidance_scale
|
||||
assert (resolved.height, resolved.width) == (preset.height, preset.width)
|
||||
|
||||
def test_explicit_value_equal_to_old_schema_default_wins(self) -> None:
|
||||
# gs=1.0 was the old schema default; it must still be honored
|
||||
# when the caller sets it deliberately against a gs=3.0 preset.
|
||||
resolved = _resolve(GenerationRequest(prompt="a fox", sampling=SamplingConfig(guidance_scale=1.0)))
|
||||
assert resolved.guidance_scale == 1.0
|
||||
|
||||
def test_negative_prompt_none_inherits_empty_string_clears(self, preset: SamplingParam) -> None:
|
||||
inherited = _resolve(GenerationRequest(prompt="x"))
|
||||
assert inherited.negative_prompt == preset.negative_prompt
|
||||
cleared = _resolve(GenerationRequest(prompt="x", negative_prompt=""))
|
||||
assert cleared.negative_prompt == ""
|
||||
|
||||
def test_parsed_dict_request_also_inherits(self) -> None:
|
||||
from fastvideo.api.parser import parse_config
|
||||
resolved = _resolve(GenerationRequest(prompt="a fox"))
|
||||
parsed_resolved = _resolve(parse_config(GenerationRequest, {"prompt": "a fox"}))
|
||||
assert parsed_resolved.num_inference_steps == resolved.num_inference_steps
|
||||
assert parsed_resolved.guidance_scale == resolved.guidance_scale
|
||||
|
||||
def test_explicitly_set_unsupported_field_still_raises(self) -> None:
|
||||
# true_cfg_scale exists on SamplingConfig but not on Wan's
|
||||
# SamplingParam; setting it explicitly must fail loudly (the old
|
||||
# schema-default tolerance is gone).
|
||||
with pytest.raises(ValueError, match="true_cfg_scale"):
|
||||
_resolve(GenerationRequest(prompt="x", sampling=SamplingConfig(true_cfg_scale=2.0)))
|
||||
|
||||
|
||||
class TestParsedNullsInherit:
|
||||
"""YAML/JSON `null` must behave exactly like an unset field — it is
|
||||
parsed into None but never bound as an explicit path."""
|
||||
|
||||
def test_parsed_sampling_null_inherits_preset(self, preset: SamplingParam) -> None:
|
||||
from fastvideo.api.parser import parse_config
|
||||
parsed = parse_config(GenerationRequest, {"prompt": "x", "sampling": {"num_frames": None, "height": None}})
|
||||
resolved = _resolve(parsed)
|
||||
assert resolved.num_frames == preset.num_frames
|
||||
assert resolved.height == preset.height
|
||||
|
||||
def test_parsed_negative_prompt_null_inherits_preset(self, preset: SamplingParam) -> None:
|
||||
from fastvideo.api.parser import parse_config
|
||||
parsed = parse_config(GenerationRequest, {"prompt": "x", "negative_prompt": None})
|
||||
assert _resolve(parsed).negative_prompt == preset.negative_prompt
|
||||
|
||||
def test_direct_none_valued_extension_is_dropped_not_resurrected(self) -> None:
|
||||
# A None-valued extension entry is pruned from the explicit paths;
|
||||
# it must not resurface via the live-attribute read in
|
||||
# explicit_request_updates and raise as an unsupported field.
|
||||
request = GenerationRequest(prompt="x", extensions={"bogus_key": None})
|
||||
resolved = _resolve(request)
|
||||
assert resolved.num_inference_steps == 3 # preset intact, no ValueError
|
||||
@@ -15,7 +15,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from fastvideo.api.compat import generator_config_to_fastvideo_args
|
||||
from fastvideo.api.translation import generator_config_to_fastvideo_args
|
||||
from fastvideo.api.schema import (
|
||||
EngineConfig,
|
||||
GeneratorConfig,
|
||||
|
||||
@@ -34,7 +34,7 @@ from fastvideo.api import (
|
||||
GeneratorConfig,
|
||||
GenerationRequest,
|
||||
)
|
||||
from fastvideo.api.compat import (
|
||||
from fastvideo.api.translation import (
|
||||
legacy_from_pretrained_to_config,
|
||||
legacy_generate_call_to_request,
|
||||
normalize_generation_request,
|
||||
@@ -205,7 +205,7 @@ class TestDreamverseNoPrivateImports:
|
||||
[
|
||||
"fastvideo",
|
||||
"fastvideo.api",
|
||||
"fastvideo.api.compat", # public in that it's re-exported
|
||||
"fastvideo.api.translation", # public in that it's re-exported
|
||||
],
|
||||
)
|
||||
def test_public_imports_resolve(self, import_path):
|
||||
|
||||
@@ -269,7 +269,7 @@ class TestNoInternalImports:
|
||||
"fastvideo.pipelines.",
|
||||
"fastvideo.configs.",
|
||||
"fastvideo.fastvideo_args",
|
||||
"fastvideo.api.compat",
|
||||
"fastvideo.api.translation",
|
||||
"fastvideo.api.parser",
|
||||
"fastvideo.api.overrides",
|
||||
"fastvideo.api.errors",
|
||||
|
||||
@@ -199,7 +199,7 @@ class TestHealthCheckRequest:
|
||||
assert req.output.return_frames is False
|
||||
|
||||
def test_round_trips_through_normalization(self):
|
||||
from fastvideo.api.compat import normalize_generation_request
|
||||
from fastvideo.api.translation import normalize_generation_request
|
||||
from fastvideo.entrypoints.video_generator import VideoGenerator
|
||||
|
||||
req = VideoGenerator.default_health_check_request()
|
||||
|
||||
@@ -224,6 +224,28 @@ class TestMockServer:
|
||||
app = build_mock_app()
|
||||
assert isinstance(app, FastAPI)
|
||||
|
||||
def test_build_app_rejects_unpinned_default_sampling(self):
|
||||
import pytest
|
||||
|
||||
from fastvideo.entrypoints.streaming.mock_server import MockGenerator
|
||||
from fastvideo.entrypoints.streaming.server import build_app
|
||||
from fastvideo.api.schema import (
|
||||
GeneratorConfig,
|
||||
SamplingConfig,
|
||||
ServeConfig,
|
||||
StreamingConfig,
|
||||
)
|
||||
|
||||
serve_config = ServeConfig(
|
||||
generator=GeneratorConfig(model_path="/models/mock"),
|
||||
streaming=StreamingConfig(),
|
||||
)
|
||||
# fps (among others) left None = "inherit preset" — meaningless to
|
||||
# the WS encoder, so build_app must reject before serving.
|
||||
serve_config.default_request.sampling = SamplingConfig(num_frames=24, height=256, width=256)
|
||||
with pytest.raises(ValueError, match="fps"):
|
||||
build_app(serve_config, MockGenerator())
|
||||
|
||||
def test_mock_generator_produces_frames(self):
|
||||
from fastvideo.api.schema import GenerationRequest, SamplingConfig
|
||||
from fastvideo.entrypoints.streaming.mock_server import MockGenerator
|
||||
|
||||
@@ -69,7 +69,7 @@ def _patch_fastvideo_args_from_kwargs(monkeypatch):
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"fastvideo.api.compat.FastVideoArgs.from_kwargs",
|
||||
"fastvideo.api.translation.FastVideoArgs.from_kwargs",
|
||||
classmethod(fake_from_kwargs),
|
||||
)
|
||||
return captured
|
||||
@@ -261,7 +261,7 @@ def test_generate_uses_typed_request_path(monkeypatch):
|
||||
assert result.video_path == "outputs/test.mp4"
|
||||
|
||||
|
||||
def test_generate_preserves_schema_defaults_for_dataclass_request(monkeypatch):
|
||||
def test_generate_dataclass_request_inherits_preset_for_unset_fields(monkeypatch):
|
||||
generator = _new_runtime_video_generator()
|
||||
captured = {}
|
||||
|
||||
@@ -288,11 +288,18 @@ def test_generate_preserves_schema_defaults_for_dataclass_request(monkeypatch):
|
||||
)
|
||||
)
|
||||
|
||||
assert captured["sampling_param"].negative_prompt is None
|
||||
# Explicitly-set fields win; None (negative_prompt) inherits the
|
||||
# model preset instead of stomping it with the schema default.
|
||||
assert captured["sampling_param"].negative_prompt == "model default"
|
||||
assert captured["sampling_param"].num_frames == 125
|
||||
assert captured["sampling_param"].height == 720
|
||||
assert captured["sampling_param"].width == 1280
|
||||
|
||||
generator.generate(GenerationRequest(prompt="hello world", negative_prompt=""))
|
||||
assert captured["sampling_param"].negative_prompt == ""
|
||||
assert captured["sampling_param"].num_frames == 61
|
||||
assert captured["sampling_param"].height == 448
|
||||
|
||||
|
||||
def test_generate_mapping_request_preserves_model_defaults_for_omitted_fields(
|
||||
monkeypatch,
|
||||
|
||||
@@ -13,7 +13,8 @@ The base DiT uses Wan-AI/Wan2.2-TI2V-5B's VAE and google/t5gemma-9b-9b-ul2's
|
||||
encoder at inference time; neither is bundled upstream.
|
||||
|
||||
This converter takes the raw MagiHuman base DiT and emits a Diffusers-style
|
||||
directory so `VideoGenerator.from_pretrained(...)` can load it standalone:
|
||||
directory so `VideoGenerator.from_config(GeneratorConfig(model_path=...))` can
|
||||
load it standalone:
|
||||
|
||||
<output>/
|
||||
model_index.json
|
||||
|
||||
@@ -683,21 +683,23 @@ def main():
|
||||
print()
|
||||
print(" 1. Test basic generation:")
|
||||
print(" from fastvideo import VideoGenerator")
|
||||
print(f" generator = VideoGenerator.from_pretrained('{output_dir}')")
|
||||
print(" video = generator.generate_video(")
|
||||
print(" from fastvideo.api import GeneratorConfig, GenerationRequest, SamplingConfig")
|
||||
print(f" generator = VideoGenerator.from_config(GeneratorConfig(model_path='{output_dir}'))")
|
||||
print(" result = generator.generate(GenerationRequest(")
|
||||
print(" prompt='A cat playing piano',")
|
||||
print(" num_inference_steps=50")
|
||||
print(" )")
|
||||
print(" sampling=SamplingConfig(num_inference_steps=50),")
|
||||
print(" ))")
|
||||
print()
|
||||
if (output_dir / "lora" / "distilled").exists():
|
||||
print(" 2. Test distilled generation (16 steps with LoRA):")
|
||||
print(f" generator = VideoGenerator.from_pretrained('{output_dir}',")
|
||||
print(f" lora_path='{output_dir}/lora/distilled',")
|
||||
print(" lora_nickname='distilled')")
|
||||
print(" video = generator.generate_video(")
|
||||
print(" from fastvideo.api import ComponentConfig, PipelineSelection")
|
||||
print(f" generator = VideoGenerator.from_config(GeneratorConfig(model_path='{output_dir}',")
|
||||
print(f" pipeline=PipelineSelection(")
|
||||
print(f" components=ComponentConfig(lora_path='{output_dir}/lora/distilled'),")
|
||||
print(" experimental={'lora_nickname': 'distilled'})))")
|
||||
print(" result = generator.generate(GenerationRequest(")
|
||||
print(" prompt='A cat playing piano',")
|
||||
print(" num_inference_steps=16,")
|
||||
print(" guidance_scale=1.0)")
|
||||
print(" sampling=SamplingConfig(num_inference_steps=16, guidance_scale=1.0)))")
|
||||
print()
|
||||
print()
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ Example (one-shot per variant):
|
||||
--public
|
||||
|
||||
After upload, the local directory can be deleted — the HF repo is the
|
||||
source of truth. `VideoGenerator.from_pretrained("FastVideo/...")` pulls
|
||||
source of truth.
|
||||
`VideoGenerator.from_config(GeneratorConfig(model_path="FastVideo/..."))` pulls
|
||||
shards on demand.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -97,42 +97,53 @@ def generate_with_model(
|
||||
flow_shift: Optional[float] = None,
|
||||
embedded_guidance_scale: Optional[float] = None,
|
||||
) -> str:
|
||||
"""Produce a video with VideoGenerator.from_pretrained; returns video path."""
|
||||
"""Produce a video with VideoGenerator.from_config; returns video path."""
|
||||
try:
|
||||
from fastvideo import VideoGenerator # lazy import
|
||||
from fastvideo.api import (
|
||||
ComponentConfig, EngineConfig, GenerationRequest, GeneratorConfig,
|
||||
OffloadConfig, OutputConfig, PipelineSelection, SamplingConfig,
|
||||
)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(f"Failed to import fastvideo.VideoGenerator: {exc}") from exc
|
||||
|
||||
init_kwargs: Dict[str, Any] = {
|
||||
"num_gpus": 1,
|
||||
"dit_cpu_offload": True,
|
||||
"vae_cpu_offload": True,
|
||||
"text_encoder_cpu_offload": True,
|
||||
"pin_cpu_memory": True,
|
||||
}
|
||||
pipeline_kwargs: Dict[str, Any] = {}
|
||||
if lora_path:
|
||||
init_kwargs["lora_path"] = lora_path
|
||||
init_kwargs["lora_nickname"] = "extracted"
|
||||
pipeline_kwargs["components"] = ComponentConfig(lora_path=lora_path)
|
||||
pipeline_kwargs["experimental"] = {"lora_nickname": "extracted"}
|
||||
|
||||
generator = VideoGenerator.from_pretrained(model_path, **init_kwargs)
|
||||
generator = VideoGenerator.from_config(GeneratorConfig(
|
||||
model_path=model_path,
|
||||
engine=EngineConfig(
|
||||
num_gpus=1,
|
||||
offload=OffloadConfig(dit=True, vae=True, text_encoder=True, pin_cpu_memory=True),
|
||||
),
|
||||
pipeline=PipelineSelection(**pipeline_kwargs),
|
||||
))
|
||||
|
||||
gen_kwargs = {
|
||||
"height": height,
|
||||
"width": width,
|
||||
"num_frames": num_frames,
|
||||
"num_inference_steps": num_inference_steps,
|
||||
"guidance_scale": guidance_scale,
|
||||
"seed": seed,
|
||||
"output_path": output_dir,
|
||||
"output_video_name": output_name,
|
||||
"save_video": True,
|
||||
}
|
||||
extensions: Dict[str, Any] = {}
|
||||
if flow_shift is not None:
|
||||
gen_kwargs["flow_shift"] = flow_shift
|
||||
extensions["flow_shift"] = flow_shift
|
||||
if embedded_guidance_scale is not None:
|
||||
gen_kwargs["embedded_guidance_scale"] = embedded_guidance_scale
|
||||
extensions["embedded_guidance_scale"] = embedded_guidance_scale
|
||||
|
||||
result = generator.generate_video(prompt, **gen_kwargs)
|
||||
result = generator.generate(GenerationRequest(
|
||||
prompt=prompt,
|
||||
sampling=SamplingConfig(
|
||||
height=height,
|
||||
width=width,
|
||||
num_frames=num_frames,
|
||||
num_inference_steps=num_inference_steps,
|
||||
guidance_scale=guidance_scale,
|
||||
seed=seed,
|
||||
),
|
||||
output=OutputConfig(
|
||||
output_path=output_dir,
|
||||
output_video_name=output_name,
|
||||
save_video=True,
|
||||
),
|
||||
extensions=extensions,
|
||||
))
|
||||
|
||||
# best-effort cleanup of internal executors
|
||||
try:
|
||||
@@ -145,9 +156,9 @@ def generate_with_model(
|
||||
expected = Path(output_dir) / f"{output_name}.mp4"
|
||||
if expected.exists():
|
||||
return str(expected)
|
||||
# fallback: check result dict
|
||||
if isinstance(result, dict) and "video_path" in result:
|
||||
return str(result["video_path"])
|
||||
# fallback: check result video path
|
||||
if getattr(result, "video_path", None):
|
||||
return str(result.video_path)
|
||||
raise FileNotFoundError(f"Video not found at expected path: {expected}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user