Compare commits

..
Author SHA1 Message Date
Satyam Srivastava 0ef47caf86 [bugfix]: select architecture-specific Blackwell kernel targets
- Detect GB200 and GB300 as 10.0a and 10.3a
- Build future GB200 CI images with 10.0a
- Add regression tests for detection, cache keys, and build arguments
2026-10-05 15:42:48 -07:00
Satyam Srivastava e71d01648c [feat]: add selectable Modal and GB200 Kubernetes GPU CI
- Preserve existing CI and add configurable GPU backend routing
- Limit execution to 2 active PRs, 4 GPUs per PR, and 8 GPUs total
- Add persistent admission, cancellation recovery, and backend statuses
- Document deployment and add infrastructure regression tests
2026-10-01 14:44:14 -07:00
884 changed files with 20003 additions and 98019 deletions
@@ -80,36 +80,26 @@ def _run_fastvideo_pipeline(model_path: Path, params: dict[str, Any]) -> Any:
generator = VideoGenerator.from_pretrained(
str(model_path),
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"dit": False,
"vae": False,
"text_encoder": False,
},
},
},
num_gpus=1,
use_fsdp_inference=False,
dit_cpu_offload=False,
vae_cpu_offload=False,
text_encoder_cpu_offload=False,
)
try:
return generator.generate({
"prompt": params["prompt"],
"negative_prompt": params.get("negative_prompt"),
"sampling": {
"height": params.get("height"),
"width": params.get("width"),
"num_frames": params.get("num_frames"),
"fps": params.get("fps"),
"num_inference_steps": params["num_inference_steps"],
"guidance_scale": params.get("guidance_scale"),
"seed": params["seed"],
},
"output": {
"output_path": f"outputs_{_MODEL_FAMILY}/pipeline_parity",
"save_video": False,
},
})
return generator.generate_video(
prompt=params["prompt"],
negative_prompt=params.get("negative_prompt"),
output_path=f"outputs_{_MODEL_FAMILY}/pipeline_parity",
save_video=False,
height=params.get("height"),
width=params.get("width"),
num_frames=params.get("num_frames"),
fps=params.get("fps"),
num_inference_steps=params["num_inference_steps"],
guidance_scale=params.get("guidance_scale"),
seed=params["seed"],
)
finally:
generator.shutdown()
@@ -84,9 +84,7 @@ fastvideo/configs/models/dits/__init__.py
fastvideo/configs/models/encoders/__init__.py
fastvideo/configs/models/vaes/__init__.py
fastvideo/envs.py
fastvideo/api/schema.py
fastvideo/api/resolution.py
fastvideo/api/inference_resolution.py
fastvideo/fastvideo_args.py
fastvideo/distributed/**
fastvideo/layers/**
fastvideo/attention/**
+2 -6
View File
@@ -20,8 +20,7 @@ on a summary here.
- Read `docs/contributing/env_vars.md` in full.
- Decide whether the setting belongs in an environment variable or an argument
(rule 5 in the policy doc). Settings that users change per deployment are
arguments; add them as typed config fields in `fastvideo/api/schema.py`
instead.
arguments; add them through `fastvideo/fastvideo_args.py` instead.
## Inputs
@@ -39,10 +38,7 @@ on a summary here.
name to `DEPRECATED_VARIABLES`. Update the uses in `examples/`,
`scripts/`, `docs/`, `apps/`, and the tests.
2. **Read the variable with `envs.NAME.get()` inside a function.**
- In tests, change the value with `envs.NAME.override(value)`, and a variable
outside the registry with `envs.override_external(name, value)`; the
`env_overrides` fixture keeps either until the end of the test.
- Name a variable that only tests read `FASTVIDEO_TEST_*`.
- In tests, change the value with `envs.NAME.override(value)`.
- Do not call `os.environ`, `os.getenv`, or `monkeypatch.setenv` for a
FastVideo variable.
- To set a variable that another tool reads, call `envs.set_external`,
@@ -57,7 +57,7 @@ Hardcoded:
- Quality tier: **`default`**. `full_quality` is a separate, deliberate
operation.
- HF repo: `FastVideo/ssim-reference-videos` (override via
`FASTVIDEO_TEST_SSIM_REFERENCE_HF_REPO`).
`FASTVIDEO_SSIM_REFERENCE_HF_REPO`).
- Device folder: `L40S_reference_videos`.
## Prerequisites
+6
View File
@@ -22,6 +22,12 @@ else
export PERF_UPLOAD_POLICY=never
fi
# Alternate GPU backends compare against references without publishing records.
# Their worker has read-only Hub credentials; publication is an operator task.
if [ "${FASTVIDEO_CI_LOCAL_ONLY:-0}" = 1 ]; then
export PERF_UPLOAD_POLICY=never
fi
nvidia-smi \
--query-gpu=index,timestamp,clocks.sm,clocks.max.sm,power.draw,power.limit,temperature.gpu \
--format=csv -l 10 > "$PERF_REPORTS_DIR/gpu_telemetry.csv" 2>/dev/null &
+1
View File
@@ -489,6 +489,7 @@ def classify_paths(paths: list[str]) -> MergePlan:
_select_output_coverage(plan, path)
continue
if path in {
"fastvideo/fastvideo_args.py",
"fastvideo/forward_context.py",
"fastvideo/image_processor.py",
"fastvideo/registry.py",
@@ -11,6 +11,7 @@ jobs:
if: >-
github.event.context == 'direct-test-completed'
&& github.event.state == 'success'
&& (vars.CI_GPU_BACKEND == '' || vars.CI_GPU_BACKEND == 'slurm')
runs-on: ubuntu-latest
steps:
- name: Check and update aggregate status
@@ -0,0 +1,62 @@
name: Promote Selected GPU Backend Status
on:
status:
permissions:
statuses: write
concurrency:
group: gpu-ci-status-${{ github.event.sha }}-${{ vars.CI_GPU_BACKEND }}
cancel-in-progress: false
jobs:
promote:
if: >-
(vars.CI_GPU_BACKEND == 'modal' || vars.CI_GPU_BACKEND == 'vllm')
&& (github.event.context == format('gpu-ci/{0}/fastcheck-passed', vars.CI_GPU_BACKEND)
|| github.event.context == format('gpu-ci/{0}/full-suite-passed', vars.CI_GPU_BACKEND))
runs-on: ubuntu-latest
env:
SELECTED_BACKEND: ${{ vars.CI_GPU_BACKEND }}
steps:
- name: Mirror the selected backend's latest suite results
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const backend = process.env.SELECTED_BACKEND;
if (!['modal', 'vllm'].includes(backend)) {
throw new Error('Unsupported selected GPU backend');
}
const sha = context.payload.sha;
// Read current state after entering the serialized workflow. A
// delayed event must not overwrite a newer failure with success.
const statuses = await github.paginate(github.rest.repos.listCommitStatusesForRef, {
owner: context.repo.owner,
repo: context.repo.repo,
ref: sha,
per_page: 100,
});
for (const suffix of ['fastcheck-passed', 'full-suite-passed']) {
const sourceContext = `gpu-ci/${backend}/${suffix}`;
const matches = statuses.filter(status => status.context === sourceContext);
matches.sort((a, b) =>
Date.parse(b.updated_at) - Date.parse(a.updated_at) || b.id - a.id
);
const latest = matches[0];
const state = latest ? latest.state : 'pending';
if (!['pending', 'success', 'failure', 'error'].includes(state)) {
throw new Error(`Unsupported status state for ${sourceContext}`);
}
await github.rest.repos.createCommitStatus({
owner: context.repo.owner,
repo: context.repo.repo,
sha,
context: suffix,
state,
description: latest
? `${backend} ${suffix}: ${state}`
: `Waiting for ${backend} ${suffix}`,
...(latest && latest.target_url ? {target_url: latest.target_url} : {}),
});
}
+3 -2
View File
@@ -203,7 +203,8 @@ jobs:
docker buildx imagetools create "${TAG_ARGS[@]}" "${IMAGE_REFS[@]}"
docker buildx imagetools inspect "${TAGS[0]}"
# The CI runner is ARM64 like DGX Spark, but targets sm_100 rather than sm_121.
# The CI runner is ARM64 like DGX Spark, but targets sm_100a rather than sm_121.
# The architecture-specific target includes the GB200 VSA CUDA extensions.
# Publish a single-architecture variant so the self-hosted CI runner can reuse
# the exact prebuilt kernel instead of compiling it in every job.
build-ci-runner-image:
@@ -219,7 +220,7 @@ jobs:
PYTHON_VERSION=3.12
CUDA_VERSION=13.0.0
UV_TORCH_BACKEND=cu130
TORCH_CUDA_ARCH_LIST=10.0
TORCH_CUDA_ARCH_LIST=10.0a
CMAKE_BUILD_PARALLEL_LEVEL=1
FLASH_ATTN_WHEEL_TAG=cu130torch2.12
FLASH_ATTN_WHEEL_RELEASE_ARM64=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.22
+6 -8
View File
@@ -135,20 +135,18 @@ def main():
# Create a video generator with a pre-trained model
generator = VideoGenerator.from_pretrained(
"FastVideo/FastWan2.1-T2V-1.3B-Diffusers",
{"engine": {"num_gpus": 1}}, # Adjust based on your hardware
num_gpus=1, # Adjust based on your hardware
)
# Define a prompt for your video
prompt = "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes wide with interest."
# Generate the video
video = generator.generate({
"prompt": prompt,
"output": {
"output_path": "my_videos/", # Controls where videos are saved
"save_video": True,
},
})
video = generator.generate_video(
prompt,
output_path="my_videos/", # Controls where videos are saved
save_video=True
)
if __name__ == '__main__':
main()
+1 -1
View File
@@ -42,7 +42,7 @@ Near-term OSS note:
- `apps/dreamverse/dreamverse/main.py`: websocket endpoint, request handling,
session state machine, rewrite orchestration, REST routes, and stream relay
- `apps/dreamverse/dreamverse/gpu_pool.py`: GPU worker processes, warmup, model
loading, and `generate()` calls through FastVideo
loading, and `generate_video()` calls through FastVideo
- `apps/dreamverse/dreamverse/prompt_enhancer.py`: prompt enhancement, rollout
rewrite execution, provider selection, and timeout/fallback behavior
- `apps/dreamverse/dreamverse/rewrite_prompt_payload.py`: canonical rewrite request payload
@@ -52,8 +52,7 @@ import torch # noqa: E402
from fastvideo import VideoGenerator # noqa: E402
from fastvideo.api import ( # noqa: E402
ComponentConfig, CompileConfig, EngineConfig, GenerationResult, GeneratorConfig, OffloadConfig, PipelineSelection,
QuantizationConfig,
ComponentConfig, CompileConfig, EngineConfig, GeneratorConfig, OffloadConfig, PipelineSelection, QuantizationConfig,
)
DEFAULT_PROMPT = ("A cinematic drone shot over coastal cliffs at sunrise, golden "
@@ -129,9 +128,9 @@ def _build_generator_config(model_path: str, enable_compile: bool, num_gpus: int
)
def _extract_stage_times(result: GenerationResult) -> OrderedDict[str, float]:
def _extract_stage_times(result: dict) -> OrderedDict[str, float]:
out: OrderedDict[str, float] = OrderedDict()
info = result.logging_info if isinstance(result, GenerationResult) else None
info = result.get("logging_info") if isinstance(result, dict) else None
if info is None:
return out
stages = getattr(info, "stages", None)
@@ -163,25 +162,19 @@ def _do_one_run(generator: VideoGenerator, prompt: str, *, height: int, width: i
_reset_peak_gpu()
t0 = time.perf_counter()
try:
result = generator.generate({
"prompt": prompt,
"negative_prompt": "",
"sampling": {
"height": height,
"width": width,
"num_frames": num_frames,
"fps": 24,
"num_inference_steps": num_inference_steps,
"guidance_scale": 1.0,
"seed": seed,
},
"output": {
"save_video": False
},
"extensions": {
"ltx2_image_crf": 0.0
},
})
result = generator.generate_video(
prompt=prompt,
negative_prompt="",
save_video=False,
height=height,
width=width,
num_frames=num_frames,
fps=24,
num_inference_steps=num_inference_steps,
guidance_scale=1.0,
seed=seed,
ltx2_image_crf=0.0,
)
if torch.cuda.is_available():
torch.cuda.synchronize()
except Exception as exc:
+40 -52
View File
@@ -14,7 +14,6 @@ import gc
import os
import re
import time
from typing import TYPE_CHECKING
import numpy as np
import torch
@@ -35,9 +34,6 @@ from dreamverse.config import (
)
from dreamverse.generation_contracts import StepResult
if TYPE_CHECKING:
from fastvideo.api import GenerationResult
# Multi-frame decoded continuation defaults from
# examples/inference/basic/basic_ltx2_distilled_video_continuation.py.
# Overridable via environment variables.
@@ -99,8 +95,8 @@ class ContinuationState:
self.video_images = None
self.audio_latents = None
def apply_video(self, request: dict, segment_idx: int) -> None:
"""Seed the next-segment request with the cached tail frames."""
def apply_video(self, request_kwargs: dict, segment_idx: int) -> None:
"""Seed next-segment kwargs with the cached tail frames."""
if segment_idx <= 1 or not self.video_images:
return
from PIL import Image
@@ -114,21 +110,21 @@ class ContinuationState:
arr = np.clip(arr, 0, 255).astype(np.uint8)
noisy.append(Image.fromarray(arr))
cond_images = noisy
request["extensions"]["ltx2_video_conditions"] = [(
request_kwargs["ltx2_video_conditions"] = [(
cond_images,
LTX2_VIDEO_CONDITIONING_FRAME_IDX,
LTX2_VIDEO_CONDITIONING_STRENGTH,
)]
request["extensions"]["ltx2_images"] = None
request["inputs"]["image_path"] = None
request_kwargs["ltx2_images"] = None
request_kwargs["image_path"] = None
def apply_audio(
self,
request: dict,
request_kwargs: dict,
segment_idx: int,
audio_lps: float,
) -> None:
"""Seed the next-segment request with clean audio latents + denoise mask.
"""Seed next-segment kwargs with clean audio latents + denoise mask.
When audio conditioning is longer than video, extend audio
generation and shift video RoPE forward so the audio prefix
@@ -145,9 +141,9 @@ class ContinuationState:
audio_extra = max(0, AUDIO_CONDITIONING_NUM_FRAMES - LTX2_VIDEO_CONDITIONING_NUM_FRAMES)
if audio_extra > 0:
audio_num_frames = NUM_FRAMES + audio_extra
request["extensions"]["audio_num_frames"] = (audio_num_frames)
request_kwargs["audio_num_frames"] = (audio_num_frames)
prefix_sec = float(audio_extra) / 24.0
request["extensions"]["video_position_offset_sec"] = prefix_sec
request_kwargs["video_position_offset_sec"] = prefix_sec
new_duration = float(NUM_FRAMES + audio_extra) / 24.0
total_T = max(
@@ -165,8 +161,8 @@ class ContinuationState:
mask = torch.ones((B, 1, total_T, 1), dtype=torch.float32)
mask[:, :, :audio_cond_T, :] = (1.0 - AUDIO_CONDITIONING_STRENGTH)
request["extensions"]["ltx2_audio_clean_latent"] = clean
request["extensions"]["ltx2_audio_denoise_mask"] = mask
request_kwargs["ltx2_audio_clean_latent"] = clean
request_kwargs["ltx2_audio_denoise_mask"] = mask
def save_video(self, frames: list) -> None:
"""Snapshot trailing N frames as PIL images for next-segment conditioning."""
@@ -310,7 +306,7 @@ class LTX2GenerationBackend:
),
)
self.generator = VideoGenerator.from_config(generator_config)
self.generator = VideoGenerator.from_pretrained(config=generator_config)
print(f"[GPU {self.gpu_id}] After model load: {self._gpu_mem()}")
lora_stack = DREAMVERSE_LORA_STACK or ([(DREAMVERSE_LORA_PATH,
@@ -407,7 +403,7 @@ class LTX2GenerationBackend:
return
loader = ComponentLoader.for_module_type("audio_encoder", "diffusers")
enc = loader.load(audio_vae_path, self.generator.resolved_config)
enc = loader.load(audio_vae_path, self.generator.fastvideo_args)
target = getattr(enc, "model", enc)
proc = AudioProcessor(
@@ -464,59 +460,51 @@ class LTX2GenerationBackend:
prompt = self._inject_style_trigger(prompt)
request = {
"prompt": prompt,
"negative_prompt": "",
"inputs": {
"image_path": image_path if segment_idx == 1 else None
},
"sampling": {
"height": FRAME_HEIGHT,
"width": FRAME_WIDTH,
"num_frames": NUM_FRAMES,
"fps": 24,
"num_inference_steps": NUM_INFERENCE_STEPS,
"guidance_scale": 1.0,
"seed": 10,
},
"output": {
"save_video": False
},
"extensions": {
"ltx2_image_crf": 0.0,
"return_continuation_state": False,
},
}
request_kwargs = dict(
prompt=prompt,
negative_prompt="",
save_video=False,
height=FRAME_HEIGHT,
width=FRAME_WIDTH,
num_frames=NUM_FRAMES,
fps=24,
num_inference_steps=NUM_INFERENCE_STEPS,
guidance_scale=1.0,
seed=10,
ltx2_image_crf=0.0,
image_path=image_path if segment_idx == 1 else None,
return_continuation_state=False,
)
if reset_conditioning:
self.continuation.clear()
audio_lps = (DEFAULT_LTX2_AUDIO_SAMPLE_RATE / DEFAULT_LTX2_AUDIO_HOP_LENGTH / DEFAULT_LTX2_AUDIO_DOWNSAMPLE)
# Phase 1: seed the request with prior-segment conditioning.
self.continuation.apply_video(request, segment_idx)
self.continuation.apply_audio(request, segment_idx, audio_lps)
# Phase 1: seed kwargs with prior-segment conditioning.
self.continuation.apply_video(request_kwargs, segment_idx)
self.continuation.apply_audio(request_kwargs, segment_idx, audio_lps)
# Phase 2: generate.
t0 = time.perf_counter()
result = self.generator.generate(request)
result = self.generator.generate_video(**request_kwargs)
torch.cuda.synchronize()
timings["generation_ms"] = (time.perf_counter() - t0) * 1000
if isinstance(result, list):
raise RuntimeError("Expected a single GenerationResult from generate.")
frames = result.frames
if not isinstance(result, dict):
raise RuntimeError("Expected dictionary output from generate_video.")
frames = result.get("frames")
if not isinstance(frames, list) or len(frames) == 0:
raise RuntimeError("Generation did not return frames.")
audio = result.audio
audio_sample_rate = result.audio_sample_rate
audio = result.get("audio")
audio_sample_rate = result.get("audio_sample_rate")
if audio is not None and audio_sample_rate is None:
# LTX2 audio decoding stage uses 24kHz output by default.
audio_sample_rate = 24000
print(f"[GPU {self.gpu_id}] audio_sample_rate missing from result; "
f"defaulting to {audio_sample_rate}Hz")
timings["generation_time_ms"] = (result.generation_time or 0.0) * 1000
timings["generation_time_ms"] = result.get("generation_time", 0.0) * 1000
# Phase 3: snapshot continuation state for the next segment.
t_save_start = time.perf_counter()
@@ -554,7 +542,7 @@ class LTX2GenerationBackend:
self,
audio: object,
audio_sample_rate: int | None,
result: "GenerationResult",
result: dict,
segment_idx: int,
) -> torch.Tensor | None:
"""Pick which tensor to cache for next-segment audio conditioning."""
@@ -569,7 +557,7 @@ class LTX2GenerationBackend:
f"for segment {segment_idx + 1}")
return re_encoded
return None
audio_latents = result.extra.get("ltx2_audio_latents")
audio_latents = result.get("ltx2_audio_latents")
if audio_latents is not None:
print(f"[GPU {self.gpu_id}] Cached audio latents "
f"shape={tuple(audio_latents.shape)} "
@@ -79,24 +79,32 @@ class MiniMaxH3GenerationBackend:
from fastvideo import VideoGenerator
from fastvideo.api import (
AttentionConfig,
CompileConfig,
ComponentConfig,
EngineConfig,
GeneratorConfig,
MiniMaxH3Options,
OffloadConfig,
ParallelismConfig,
PipelineSelection,
)
adapter_path = hf_hub_download(repo_id=adapter_repo, filename=adapter_filename)
use_vsa = attention_backend == "VIDEO_SPARSE_ATTN_H3"
experimental = {
"attention_backend": attention_backend,
"inference_torch_compile": attention_backend == "FLASH_ATTN",
"vae_parallel_decode": True,
"vae_parallel_decode_strategy": "gather",
}
if attention_backend == "VIDEO_SPARSE_ATTN_H3":
experimental.update({
"VSA_sparsity": 0.9,
"VSA_tile_size": 64,
})
generator_config = GeneratorConfig(
model_path=model_path,
pipeline=PipelineSelection(
components=ComponentConfig(lora_path=adapter_path, lora_strength=1.0),
model=MiniMaxH3Options(vae_parallel_decode=True, vae_parallel_decode_strategy="gather"),
experimental=experimental,
),
engine=EngineConfig(
num_gpus=DREAMVERSE_SP_SIZE,
@@ -109,12 +117,7 @@ class MiniMaxH3GenerationBackend:
vae=True,
pin_cpu_memory=True,
),
compile=CompileConfig(enabled=False, vae_enabled=True, regional=attention_backend == "FLASH_ATTN"),
attention=AttentionConfig(
backend=attention_backend,
vsa_sparsity=0.9 if use_vsa else None,
vsa_tile_size=64 if use_vsa else None,
),
compile=CompileConfig(enabled=False, vae_enabled=True),
use_fsdp_inference=False,
),
)
@@ -13,6 +13,7 @@ FORBIDDEN_PREFIXES = (
"fastvideo.models",
"fastvideo.layers",
"fastvideo.worker",
"fastvideo.fastvideo_args",
)
ALLOWED_INTERNAL_IMPORTS = {
(
@@ -88,13 +88,14 @@ def test_initialize_builds_vsa_datafree_fasth3_generator(monkeypatch):
assert config.model_path == "MiniMaxAI/MiniMax-H3"
assert config.pipeline.components.lora_path.endswith("vsa-datafree/adapter_model.safetensors")
assert config.pipeline.components.lora_strength == 1.0
assert config.pipeline.experimental == {}
assert config.engine.attention.backend == "VIDEO_SPARSE_ATTN_H3"
assert config.engine.attention.vsa_sparsity == 0.9
assert config.engine.attention.vsa_tile_size == 64
assert config.engine.compile.regional is False
assert config.pipeline.model.vae_parallel_decode is True
assert config.pipeline.model.vae_parallel_decode_strategy == "gather"
assert config.pipeline.experimental == {
"attention_backend": "VIDEO_SPARSE_ATTN_H3",
"inference_torch_compile": False,
"vae_parallel_decode": True,
"vae_parallel_decode_strategy": "gather",
"VSA_sparsity": 0.9,
"VSA_tile_size": 64,
}
assert config.engine.num_gpus == 4
assert config.engine.parallelism.tp_size == 1
assert config.engine.parallelism.sp_size == 4
+2 -2
View File
@@ -207,7 +207,7 @@
</Array>
</mxGeometry>
</mxCell>
<mxCell id="e_dsg" value="generator.generate()" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#9673a6;endArrow=classic;fontSize=10;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="do_step" target="generator" edge="1">
<mxCell id="e_dsg" value="generator.generate_video()" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#9673a6;endArrow=classic;fontSize=10;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="do_step" target="generator" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="e_dscache" value="read / write" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;strokeColor=#d6b656;endArrow=classic;startArrow=classic;fontSize=10;exitX=0;exitY=0.8;exitDx=0;exitDy=0;entryX=1;entryY=0.2;entryDx=0;entryDy=0;" parent="1" source="do_step" target="caches" edge="1">
@@ -534,7 +534,7 @@
<mxPoint x="1040" y="1610" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="dm11a" value="10a. worker runs:&#xa;VideoGenerationWorker.generate_step()&#xa; (ltx2_generation.py:380)&#xa; → generator.generate()&#xa; → updates ContinuationState&#xa;then stream_fmp4() (av_streaming.py:121)&#xa; → ffmpeg (rawvideo+wav → fmp4)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe0b2;strokeColor=#d79b00;fontSize=10;align=left;spacingLeft=8;fontFamily=monospace;" parent="1" vertex="1">
<mxCell id="dm11a" value="10a. worker runs:&#xa;VideoGenerationWorker.generate_step()&#xa; (ltx2_generation.py:380)&#xa; → generator.generate_video()&#xa; → updates ContinuationState&#xa;then stream_fmp4() (av_streaming.py:121)&#xa; → ffmpeg (rawvideo+wav → fmp4)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe0b2;strokeColor=#d79b00;fontSize=10;align=left;spacingLeft=8;fontFamily=monospace;" parent="1" vertex="1">
<mxGeometry x="955" y="1640" width="180" height="70" as="geometry"/>
</mxCell>
<mxCell id="dm11" value="10b. resp_q.put(MediaInit / MediaChunk / MediaComplete / StepComplete)" style="endArrow=classic;html=1;strokeColor=#b85450;fontSize=10;labelBackgroundColor=#ffffff;" parent="1" edge="1">
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

@@ -64,8 +64,8 @@ generator:
# internal: pipeline_config.dit_config.quant_config = FP4Config()
# set in gpu_pool.py:280 (via the legacy in-place mutation). The
# public typed surface resolves "NVFP4" to NVFP4Config() and pins
# it on dit_config when resolution materializes the PipelineConfig.
# Comment this block out on hosts without flashinfer / NVFP4 hardware.
# it on dit_config in FastVideoArgs.__post_init__. Comment this
# block out on hosts without flashinfer / NVFP4 hardware.
quantization:
transformer_quant: NVFP4
@@ -67,7 +67,7 @@ test.describe('preset prompt generation', () => {
// on a B200 plus encode/transfer time. The "Continuation flipped
// to Generating + Leave button rendered" pair above is the proof
// the integration works: FE → /readyz → /curated-presets → WS
// /ws → BE → GPU pool → VideoGenerator.generate, all green.
// /ws → BE → GPU pool → VideoGenerator.generate_video, all green.
const video = page.locator('video').first();
await expect(video).toHaveCount(1);
});
+33 -53
View File
@@ -862,40 +862,23 @@ class JobRunner:
sp_size,
)
gen = VideoGenerator.from_config(
{
"model_path": model_id,
"engine": {
"num_gpus": num_gpus,
"parallelism": {
"tp_size": tp_size,
"sp_size": sp_size,
},
"offload": {
"dit": dit_cpu_offload,
"dit_layerwise": dit_layerwise_offload,
"text_encoder": text_encoder_cpu_offload,
"image_encoder": image_encoder_cpu_offload,
"vae": vae_cpu_offload,
},
"compile": {
"enabled": enable_torch_compile
},
"attention": {
"vsa_sparsity": vsa_sparsity
},
"use_fsdp_inference": use_fsdp_inference,
},
"pipeline": {
"workload_type":
workload_type,
**({
"components": {
"override_pipeline_cls_name": override_pipeline_cls_name
}
} if override_pipeline_cls_name else {}),
},
},
gen = VideoGenerator.from_pretrained(
model_id,
workload_type=workload_type,
num_gpus=num_gpus,
dit_layerwise_offload=dit_layerwise_offload,
**({
"override_pipeline_cls_name": override_pipeline_cls_name
} if override_pipeline_cls_name else {}),
dit_cpu_offload=dit_cpu_offload,
text_encoder_cpu_offload=text_encoder_cpu_offload,
vae_cpu_offload=vae_cpu_offload,
image_encoder_cpu_offload=image_encoder_cpu_offload,
use_fsdp_inference=use_fsdp_inference,
enable_torch_compile=enable_torch_compile,
VSA_sparsity=vsa_sparsity,
tp_size=tp_size,
sp_size=sp_size,
log_queue=log_queue,
)
@@ -1120,33 +1103,30 @@ class JobRunner:
# Without a name FastVideo derives the filename from the prompt.
safe_name = re.sub(r'[\\/:*?"<>|]+', "", job.name).strip().strip(".")
output_target = (os.path.join(job_output_dir, f"{safe_name[:80]}.mp4") if safe_name else job_output_dir)
request: dict[str, Any] = {
gen_kwargs: dict[str, Any] = {
"prompt": job.prompt,
"output_path": output_target,
"save_video": True,
"num_inference_steps": job.num_inference_steps,
"num_frames": job.num_frames,
"height": job.height,
"width": job.width,
"guidance_scale": job.guidance_scale,
"guidance_rescale": job.guidance_rescale,
"fps": job.fps,
"seed": job.seed,
"negative_prompt": job.negative_prompt or "",
"sampling": {
"num_inference_steps": job.num_inference_steps,
"num_frames": job.num_frames,
"height": job.height,
"width": job.width,
"guidance_scale": job.guidance_scale,
"guidance_rescale": job.guidance_rescale,
"fps": job.fps,
"seed": job.seed,
},
"output": {
"output_path": output_target,
"save_video": True,
},
"log_queue": log_queue,
}
if job.image_path:
request.setdefault("inputs", {})["image_path"] = job.image_path
gen_kwargs["image_path"] = job.image_path
if job.references:
request.setdefault("inputs", {})["references"] = _build_h3_references(job.references)
gen_kwargs["references"] = _build_h3_references(job.references)
if job.last_image_path:
# _prepare_fl2va requires a PIL image, not a path.
from PIL import Image as _PILImage
request.setdefault("inputs", {})["last_image"] = _PILImage.open(job.last_image_path)
generator.generate(request, log_queue=log_queue)
gen_kwargs["last_image"] = _PILImage.open(job.last_image_path)
generator.generate_video(**gen_kwargs)
buf.phase = "saving"
logger.info("Generation completed, searching for output file...")
@@ -131,9 +131,8 @@ export default function CreateJobModal({
const editingJobId = editingJob?.id ?? null;
const editingJobModelId = editingJob?.model_id ?? null;
// Layerwise offload and FSDP compete for the DiT weights and the device offload
// policy (resolve_device_offload_conflicts in fastvideo/api/device_policy.py)
// silently picks a winner; resolve it visibly here.
// Layerwise offload and FSDP compete for the DiT weights and FastVideoArgs
// silently picks a winner (fastvideo_args.py:859); resolve it visibly here.
// dit_cpu_offload is deliberately not interlocked -- it is a modifier, not a
// competing strategy.
const handleDitLayerwiseOffloadChange = React.useCallback((next: boolean) => {
+4 -32
View File
@@ -15,9 +15,6 @@ from fastvideo import VideoGenerator as FastVideoGenerator
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
# InferenceArgs keys that are SamplingConfig fields of a GenerationRequest.
_SAMPLING_INFERENCE_ARGS = ("height", "width", "num_frames", "num_inference_steps", "guidance_scale", "seed", "fps")
# Custom exception for interruption
class GenerationInterruptedException(Exception):
@@ -157,17 +154,7 @@ class VideoGenerator:
"""Thread function to run the generation"""
try:
if self.generator is not None:
# Place each InferenceArgs value in the GenerationRequest section that owns it.
request: dict[str, Any] = {"prompt": prompt, "output": {"output_path": output_path}}
for key, value in inference_args.items():
if key == "image_path":
section = "inputs"
elif key in _SAMPLING_INFERENCE_ARGS:
section = "sampling"
else:
section = "extensions"
request.setdefault(section, {})[key] = value
self.generator.generate(request)
self.generator.generate_video(prompt=prompt, output_path=output_path, **inference_args)
self._generation_result = os.path.join(output_path, f"{prompt[:100]}.mp4")
else:
raise RuntimeError("Generator is not initialized")
@@ -266,24 +253,9 @@ class VideoGenerator:
if self.generator is None:
print('generation_args', generation_args)
print('pipeline_config', pipeline_config)
# Place each generation argument at its GeneratorConfig engine path.
engine_config: dict[str, Any] = {}
if "num_gpus" in generation_args:
engine_config["num_gpus"] = generation_args["num_gpus"]
for parallelism_key in ("tp_size", "sp_size"):
if parallelism_key in generation_args:
engine_config.setdefault("parallelism", {})[parallelism_key] = generation_args[parallelism_key]
if "dit_cpu_offload" in generation_args:
engine_config["offload"] = {"dit": generation_args["dit_cpu_offload"]}
self.generator = FastVideoGenerator.from_config({
"model_path": model_path,
"engine": engine_config,
"pipeline": {
"experimental": {
"pipeline_config": pipeline_config
}
},
})
self.generator = FastVideoGenerator.from_pretrained(model_path=model_path,
**generation_args,
pipeline_config=pipeline_config)
print('inference_args', inference_args)
+1 -1
View File
@@ -544,7 +544,7 @@
"limitations": [
"Install from the DGX Spark guide, not the generic CUDA extra. GB10 has no FA4 / sm_100a VSA kernel; keep FASTVIDEO_FA4=0 and FASTVIDEO_VSA_SM100A=0.",
"Legal num_frames values are 17n+5, capped at 345 (15 s). Native 16:9 sizes include 832x480 and 1344x768.",
"Lazy module load reloads Qwen3-VL and the DiT between phases of each request. Do not set engine.offload.lazy_module_load to false on this box.",
"Lazy module load reloads Qwen3-VL and the DiT between phases of each request. Do not pass --no-lazy-module-load on this box.",
"A 345-frame request on one Spark can OOM. Prefer 124 or 243 frames, TAEH3 decode, or two Sparks over QSFP."
]
},
+10 -2
View File
@@ -4,6 +4,11 @@ This is the canonical reference for FastVideo's CI/CD system. Contributor-facing
PR steps live in [Pull Requests](pull_requests.md), and test-authoring guidance
lives in [Testing](testing.md).
The existing Slurm route below remains the default. Operators can also install
the [selectable GPU dispatcher](gpu_ci_backends.md) to run the same lane scripts
on Modal or Kubernetes in the `vllm` namespace. That opt-in path enforces two
active PRs, four GPUs per PR, and eight total, with separate backend statuses.
## Overview
FastVideo splits validation across GitHub Actions, Buildkite, Slinky Slurm,
@@ -435,8 +440,11 @@ before later jobs consume the updated image pin.
The same workflow publishes a single-architecture ARM64, CUDA 13, SM100 image
for the self-hosted CI runner under the
`py3.12-cuda13.0.0-sm100-{latest,sha-*}` tags. It carries the matching prebuilt
kernel wheel so runner jobs can validate and install the exact source and ABI
match instead of recompiling it in every lane.
kernel wheel compiled with `TORCH_CUDA_ARCH_LIST=10.0a` to include the GB200
VSA CUDA extensions. Runtime kernel detection uses the same target, so runner
jobs can validate and install the exact source and ABI match instead of
recompiling it in every lane. Older artifacts built for `10.0` have a different
cache key and trigger a local rebuild when the worker detects `10.0a`.
The optional Dreamverse matrix builds backend and UI images for CUDA 12.6 and
CUDA 13 on `amd64`. Dreamverse remains `amd64`-only because its FA4 dependency
+10 -6
View File
@@ -57,15 +57,19 @@ Minimal usage example (based on `examples/inference/basic/basic.py`):
```python
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
model_id = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" # or official_weights/<model_name>/
generator = VideoGenerator.from_pretrained(model_id, {"engine": {"num_gpus": 1}})
generator = VideoGenerator.from_pretrained(model_id, num_gpus=1)
video = generator.generate({
"prompt": "A vibrant city street at sunset.",
"sampling": {"num_frames": 45},
"output": {"output_path": "video_samples", "save_video": True},
})
sampling = SamplingParam.from_pretrained(model_id)
sampling.num_frames = 45
video = generator.generate_video(
"A vibrant city street at sunset.",
sampling_param=sampling,
output_path="video_samples",
save_video=True,
)
```
## Some questions to ask yourself before starting
+13 -65
View File
@@ -13,15 +13,12 @@ test in the same pull request.
them directly with `os.environ.get("NAME")`, and the name must be in the external-variable allowlist
(`EXTERNAL_ALLOWLIST` in the contract test). When FastVideo sets such a variable for the other tool, it calls
`envs.set_external`, `envs.setdefault_external`, or `envs.unset_external`, and the name must be in
`EXTERNAL_WRITE_ALLOWLIST`. Variables that FastVideo's CI and CI tooling define (for example `TEST_SCOPE` and
`PERF_RUN_SOURCE`) keep their names, and test code under `fastvideo/tests/` reads them directly; they are listed
in `CI_ONLY_VARIABLES` in the contract test, together with the file that sets each one.
`EXTERNAL_WRITE_ALLOWLIST`.
2. **Read with `envs.NAME.get()`, write with `envs.NAME.set()`, and change a value in tests with
`envs.NAME.override()`.** Each type has one parsing rule. A value that the rule rejects raises
`fastvideo.envs.EnvVarError` instead of falling back to the default.
3. **Name FastVideo variables with the `FASTVIDEO_` prefix.** The second word states the purpose where one applies:
`ENABLE_`, `DISABLE_`, `USE_`, `FORCE_`, `DEBUG_`, `TEST_`. Variables that only tests read use
`FASTVIDEO_TEST_`, for example `FASTVIDEO_TEST_SD35_MODEL_DIR`, and the category `test`.
`ENABLE_`, `DISABLE_`, `USE_`, `FORCE_`, `DEBUG_`, `TEST_`.
4. **Keep a renamed variable as a deprecated alias until the next minor release.** Setting the old name logs a
warning. Delete a variable that no code reads, and list it in `DEPRECATED_VARIABLES` so that setting it logs a
warning.
@@ -32,8 +29,7 @@ test in the same pull request.
effect without re-importing a module. Module level, class bodies, decorators, and default argument values run at
import time.
7. **Do not write the environment to pass values between parts of FastVideo.** Pass an argument instead. Tests use
`envs.NAME.override()`, and `envs.override_external()` for variables outside the registry; both restore the
previous value.
`envs.NAME.override()`.
## Field types
@@ -91,21 +87,6 @@ with envs.FASTVIDEO_DEBUG_MY_STAGE.override(True):
run_stage()
```
For a variable outside the registry, such as `MASTER_PORT` or `TEST_SCOPE`, use `envs.override_external`. To keep
an override until the end of a test, enter it through the `env_overrides` fixture from `fastvideo/tests/conftest.py`,
which restores every value at teardown:
```python
def test_my_stage(env_overrides):
env_overrides.enter_context(envs.FASTVIDEO_DEBUG_MY_STAGE.override(True))
env_overrides.enter_context(envs.override_external("MASTER_PORT", "29512"))
run_stage()
```
In test code under `fastvideo/tests/`, `override_external` accepts any name that code may read directly
(`EXTERNAL_ALLOWLIST`, `CI_ONLY_VARIABLES`) or that is in `EXTERNAL_WRITE_ALLOWLIST`. Library code may write only
the names in `EXTERNAL_WRITE_ALLOWLIST`.
## Rename or remove a variable
To rename a variable, declare it under the new name and list the old name in `deprecated_names`:
@@ -122,9 +103,8 @@ in `examples/`, `scripts/`, `docs/`, `apps/`, and the tests in the same pull req
minor release.
To remove a variable that no code reads, delete its entry and add the name to `DEPRECATED_VARIABLES` in
`fastvideo/envs.py` with a reason. The config resolution step `warn_deprecated_environment_variables` in
`fastvideo/api/inference_resolution.py` calls `envs.warn_deprecated_variables()`, which logs a warning for each listed
variable that is set. Delete the entry in the next minor release.
`fastvideo/envs.py` with a reason. `FastVideoArgs` calls `envs.warn_deprecated_variables()`, which logs a warning for
each listed variable that is set. Delete the entry in the next minor release.
## What the contract test checks
@@ -141,11 +121,9 @@ It reports each violation as `<path>: <kind> <name>`:
| | with a name built at runtime (`<dynamic>`) | another tool owns, add it to |
| | | `EXTERNAL_ALLOWLIST` with a reason. |
| `write` | `os.environ[...] = ...`, `setdefault`, `pop`, `del`, | Pass an argument instead. In tests, use |
| | `os.putenv`, `os.unsetenv`, `monkeypatch.setenv`/`delenv`, | `envs.NAME.override()`, or |
| | or an `envs.*_external` call with a name that the | `envs.override_external()` for a variable |
| | helper does not accept | outside the registry. For a variable that |
| | | another tool reads, call an |
| | | `envs.*_external` helper and add the name to |
| | `os.putenv`, `os.unsetenv`, `monkeypatch.setenv`/`delenv`, | `envs.NAME.override()`. For a variable that |
| | or an `envs.*_external` call with a name outside | another tool reads, call an |
| | `EXTERNAL_WRITE_ALLOWLIST` | `envs.*_external` helper and add the name to |
| | | `EXTERNAL_WRITE_ALLOWLIST` with a reason. |
| `whole-environ` | `os.environ.copy()`, `dict(os.environ)`, iteration, | Read the specific variables that the code |
| | `mock.patch.dict(os.environ, ...)`, `os.environ.update` | needs. |
@@ -189,7 +167,7 @@ longer exists also fails the test, so the fixing pull request deletes its entry.
| `FASTVIDEO_LOGGING_LEVEL` | str | `INFO` | logging | Default logging level. |
| `FASTVIDEO_LOGGING_PREFIX` | str | `""` | logging | Prefix prepended to every log message. |
| `FASTVIDEO_STAGE_LOGGING` | bool | `0` | logging | Log the time that each pipeline stage takes. |
| `FASTVIDEO_ATTENTION_BACKEND` | str | unset | attention | Attention backend, as an AttentionBackendEnum name such as TORCH_SDPA, FLASH_ATTN, VIDEO_SPARSE_ATTN, SAGE_ATTN, or SAGE_ATTN_THREE. Config resolution uses it when engine.attention.backend is unset. An unsupported name raises an error. |
| `FASTVIDEO_ATTENTION_BACKEND` | str | unset | attention | Attention backend, as an AttentionBackendEnum name such as TORCH_SDPA, FLASH_ATTN, VIDEO_SPARSE_ATTN, SAGE_ATTN, or SAGE_ATTN_THREE. FastVideoArgs uses it when FastVideoArgs.attention_backend is unset. |
| `FASTVIDEO_FA4` | bool | `0` | attention | The FLASH_ATTN backend uses FlashAttention-4 (flash_attn.cute) instead of FA3 or FA2. |
| `FASTVIDEO_MINIMAX_H3_FA4_PACKED_VARLEN` | bool | `0` | attention | MiniMax-H3 dense DiT self-attention uses the FlashAttention-4 packed-varlen entry point. This changes the floating-point reduction order, so it is an inference-only opt-in. |
| `FASTVIDEO_VSA_SM100A` | bool | `0` | attention | VIDEO_SPARSE_ATTN_H3 sends no-grad tile-64 forwards to the data-center Blackwell (sm_100a) kernel. fastvideo-kernel reads the same variable with the same rule. |
@@ -197,9 +175,9 @@ longer exists also fails the test, so the fixing pull request deletes its entry.
| `FASTVIDEO_DISABLE_ATTENTION_COMPILE` | bool | `1` | attention | Keep attention forward out of torch.compile graphs (torch.compiler.disable). Set it to 0 to let attention constructed under that setting be traced. Setting it explicitly to true also blocks regional compile. |
| `FASTVIDEO_MLX_WINDOW` | int | `0` | attention | MLX FastWan windowed attention size in tokens. 0 uses full attention. |
| `FASTVIDEO_MLX_WINDOW_SINK` | int | `0` | attention | Number of sink tokens that MLX windowed attention always attends to. |
| `FASTVIDEO_INFERENCE_TORCH_COMPILE` | bool | `0` | performance | Compile each DiT transformer block with fullgraph torch.compile at inference. Same as engine.compile.regional=True. |
| `FASTVIDEO_VAE_PARALLEL_DECODE` | bool | `0` | performance | MiniMax-H3 VAE decode splits its temporal chunks across the sequence-parallel ranks instead of running serially on the output rank. Same as pipeline.model.minimax_h3.vae_parallel_decode=True. |
| `FASTVIDEO_VAE_PARALLEL_ENCODE` | bool | `0` | performance | MiniMax-H3 reference-video VAE encode splits its temporal chunks across the sequence-parallel ranks. Same as pipeline.model.minimax_h3.vae_parallel_encode=True. |
| `FASTVIDEO_INFERENCE_TORCH_COMPILE` | bool | `0` | performance | Compile each DiT transformer block with fullgraph torch.compile at inference. Same as FastVideoArgs.inference_torch_compile=True. |
| `FASTVIDEO_VAE_PARALLEL_DECODE` | bool | `0` | performance | MiniMax-H3 VAE decode splits its temporal chunks across the sequence-parallel ranks instead of running serially on the output rank. Same as FastVideoArgs.vae_parallel_decode=True. |
| `FASTVIDEO_VAE_PARALLEL_ENCODE` | bool | `0` | performance | MiniMax-H3 reference-video VAE encode splits its temporal chunks across the sequence-parallel ranks. Same as FastVideoArgs.vae_parallel_encode=True. |
| `FASTVIDEO_VAE_PARALLEL_DECODE_STRATEGY` | str | unset | performance | Collective that moves chunks in parallel VAE decode: gather (used when unset) or all_gather. |
| `FASTVIDEO_MINIMAX_H3_FUSIONS` | str | `""` | performance | MiniMax-H3 inference-only Triton fusions: all, 1, or a comma-separated subset of modulate,qknorm_rope,swiglu. Empty, 0, or none keeps the eager implementation. |
| `FASTVIDEO_FSDP2_AUTOWRAP` | bool | `0` | performance | FSDP2 shards modules by parameter count instead of the model's shard conditions. Not supported by self-forcing distillation. |
@@ -235,7 +213,7 @@ longer exists also fails the test, so the fixing pull request deletes its entry.
| `FASTVIDEO_LTX2_GEMMA_LOG` | str | `""` | debug | Log file for LTX-2 Gemma text-encoder hidden states, used by parity tests. Deprecated names: `LTX2_FASTVIDEO_GEMMA_LOG`. |
| `FASTVIDEO_COSMOS25_LOG_KNOBS` | bool | `0` | debug | Log the Cosmos 2.5 latent-preparation conditioning inputs. |
| `FASTVIDEO_CFG_GATE_STEP` | float | `1.0` | sampling | CFG gating fraction in [0, 1]. Steps before len(timesteps) \* X run the conditional and unconditional forwards; later steps reuse the cached difference. 1.0 disables gating. |
| `FASTVIDEO_LTX2_USE_DISTILLED_SIGMAS` | bool | `1` | sampling | LTX-2 uses the distilled sigma schedule when pipeline.model.ltx2.use_distilled_sigmas is also true. Deprecated names: `LTX2_USE_DISTILLED_SIGMAS`. |
| `FASTVIDEO_LTX2_USE_DISTILLED_SIGMAS` | bool | `1` | sampling | LTX-2 uses the distilled sigma schedule when FastVideoArgs.ltx2_use_distilled_sigmas is also true. Deprecated names: `LTX2_USE_DISTILLED_SIGMAS`. |
| `FASTVIDEO_EVAL_CACHE` | path | computed | eval | Cache directory for evaluation models and datasets. Defaults to $FASTVIDEO_CACHE_ROOT/eval. |
| `FASTVIDEO_PHYSICS_IQ_BUCKET_URL` | str | `https://storage.googleapis.com/physics-iq-benchmark` | eval | Base URL of the Physics-IQ benchmark bucket. |
| `FASTVIDEO_VBENCH_FULL_INFO_JSON` | str | unset | eval | Path to VBench_full_info.json, used instead of the vendored copy. Deprecated names: `VBENCH_FULL_INFO_JSON`. |
@@ -249,36 +227,6 @@ longer exists also fails the test, so the fixing pull request deletes its entry.
| `FASTVIDEO_TEST_LTX2_OVERFIT_NUM_COPIES` | int | `4` | test | Number of copies of the overfit sample in the parquet file. Deprecated names: `LTX2_OVERFIT_NUM_COPIES`. |
| `FASTVIDEO_TEST_KANDINSKY5_OVERFIT_DATA_DIR` | str | `data/kandinsky5_overfit` | test | Raw data directory for preprocess_kandinsky5_overfit.py. Deprecated names: `KANDINSKY5_OVERFIT_DATA_DIR`. |
| `FASTVIDEO_TEST_KANDINSKY5_OVERFIT_OUTPUT_DIR` | str | `data/kandinsky5_overfit_preprocessed` | test | Output directory for preprocess_kandinsky5_overfit.py. Deprecated names: `KANDINSKY5_OVERFIT_OUTPUT_DIR`. |
| `FASTVIDEO_TEST_SSIM_REFERENCE_HF_REPO` | str | `FastVideo/ssim-reference-videos` | test | Hugging Face repository that holds the SSIM reference videos. Deprecated names: `FASTVIDEO_SSIM_REFERENCE_HF_REPO`. |
| `FASTVIDEO_TEST_SSIM_REFERENCE_HF_REPO_TYPE` | str | `dataset` | test | Repository type of FASTVIDEO_TEST_SSIM_REFERENCE_HF_REPO. Deprecated names: `FASTVIDEO_SSIM_REFERENCE_HF_REPO_TYPE`. |
| `FASTVIDEO_TEST_SSIM_SKIP_REFERENCE_DOWNLOAD` | bool | `0` | test | SSIM tests use local reference videos without downloading. Deprecated names: `FASTVIDEO_SSIM_SKIP_REFERENCE_DOWNLOAD`. |
| `FASTVIDEO_TEST_SSIM_FULL_QUALITY` | bool | `0` | test | SSIM tests use the full-quality sampling configurations. Deprecated names: `FASTVIDEO_SSIM_FULL_QUALITY`. |
| `FASTVIDEO_TEST_NIGHTLY` | bool | `0` | test | Run the nightly end-to-end overfit tests. Deprecated names: `FASTVIDEO_NIGHTLY`. |
| `FASTVIDEO_TEST_ULYSSES_FAULT_RANK` | str | unset | test | Rank that fails in the Ulysses fault-injection test. The test sets it for its worker processes. Deprecated names: `FASTVIDEO_ULYSSES_FAULT_RANK`. |
| `FASTVIDEO_TEST_ULYSSES_FAULT_STAGE` | str | unset | test | Stage that fails in the Ulysses fault-injection test. The test sets it for its worker processes. Deprecated names: `FASTVIDEO_ULYSSES_FAULT_STAGE`. |
| `FASTVIDEO_TEST_GOLDEN_GATE_DIR` | str | unset | test | Local directory of golden-gate reference tensors. Deprecated names: `FASTVIDEO_GOLDEN_GATE_DIR`. |
| `FASTVIDEO_TEST_WAN22_5B_ALLOW_LOW_MEMORY` | bool | `0` | test | Run the MLX Wan2.2 5B real-weights parity test on hosts with little memory. Deprecated names: `FASTVIDEO_WAN22_5B_ALLOW_LOW_MEMORY`. |
| `FASTVIDEO_TEST_WAN22_5B_ROOT` | str | unset | test | Local Wan2.2 5B checkpoint for the MLX real-weights parity test. Deprecated names: `FASTVIDEO_WAN22_5B_ROOT`. |
| `FASTVIDEO_TEST_GRADNORM_UPDATE` | bool | `0` | test | Gradient-norm regression tests update their references. Deprecated names: `FASTVIDEO_GRADNORM_UPDATE`. |
| `FASTVIDEO_TEST_DREAMX_WORLD_SSIM_MODEL_PATH` | str | `FastVideo/DreamX-World-5B-Cam-Diffusers` | test | Model for the DreamX-World camera SSIM test. Deprecated names: `DREAMX_WORLD_SSIM_MODEL_PATH`. |
| `FASTVIDEO_TEST_DREAMX_WORLD_AR_SSIM_MODEL_PATH` | str | `FastVideo/DreamX-World-5B-Diffusers` | test | Model for the DreamX-World autoregressive SSIM test. Deprecated names: `DREAMX_WORLD_AR_SSIM_MODEL_PATH`. |
| `FASTVIDEO_TEST_FLUX_T2I_MODEL_DIR` | str | `black-forest-labs/FLUX.1-dev` | test | Model for the Flux text-to-image SSIM test. Deprecated names: `FLUX_T2I_MODEL_DIR`. |
| `FASTVIDEO_TEST_FLUX_TRANSFORMER_PATH` | str | unset | test | Local Flux transformer for the Flux transformer test. Deprecated names: `FLUX_TRANSFORMER_PATH`. |
| `FASTVIDEO_TEST_GAMECRAFT_MODEL_PATH` | str | `FastVideo/HunyuanGameCraft-Diffusers` | test | Model for the HunyuanGameCraft SSIM test. Deprecated names: `GAMECRAFT_MODEL_PATH`. |
| `FASTVIDEO_TEST_GEN3C_MODEL_PATH` | str | `FastVideo/GEN3C-Cosmos-7B-Diffusers` | test | Model for the GEN3C SSIM test. Deprecated names: `GEN3C_MODEL_PATH`. |
| `FASTVIDEO_TEST_GEN3C_IMAGE_PATH` | str | unset | test | Input image for the GEN3C SSIM test. Deprecated names: `GEN3C_TEST_IMAGE_PATH`. |
| `FASTVIDEO_TEST_GLM_IMAGE_LOCAL_WEIGHTS_DIR` | str | unset | test | Local official GLM-Image weights for the GLM-Image SSIM test. Deprecated names: `GLM_IMAGE_LOCAL_WEIGHTS_DIR`. |
| `FASTVIDEO_TEST_GLM_IMAGE_MODEL_DIR` | str | unset | test | Model for the GLM-Image SSIM test. Deprecated names: `GLM_IMAGE_MODEL_DIR`. |
| `FASTVIDEO_TEST_KANDINSKY5_E2E_NUM_GPUS` | int | `1` | test | GPUs for the Kandinsky5 nightly end-to-end overfit test. Deprecated names: `KANDINSKY5_E2E_NUM_GPUS`. |
| `FASTVIDEO_TEST_KANDINSKY5_E2E_WRITE_REFERENCE` | bool | `0` | test | The Kandinsky5 nightly end-to-end test writes a missing reference video. Deprecated names: `KANDINSKY5_E2E_WRITE_REFERENCE`. |
| `FASTVIDEO_TEST_LONGCAT_MODEL_ROOT` | str | unset | test | Local LongCat-Video checkpoint for the golden-gate test. Deprecated names: `LONGCAT_MODEL_ROOT`. |
| `FASTVIDEO_TEST_MINIMAX_H3_GATE_GOLDEN_DIR` | str | unset | test | Local directory of MiniMax-H3 golden-gate tensors. Deprecated names: `MINIMAX_H3_GATE_GOLDEN_DIR`. |
| `FASTVIDEO_TEST_MINIMAX_H3_GATE_LAYER` | int | `0` | test | Transformer layer that the MiniMax-H3 golden-gate test checks. Deprecated names: `MINIMAX_H3_GATE_LAYER`. |
| `FASTVIDEO_TEST_MINIMAX_H3_MODEL_ROOT` | str | unset | test | Local MiniMax-H3 checkpoint for the golden-gate test. Deprecated names: `MINIMAX_H3_MODEL_ROOT`. |
| `FASTVIDEO_TEST_SD35_MODEL_DIR` | str | `stabilityai/stable-diffusion-3.5-medium` | test | Model for the Stable Diffusion 3.5 SSIM test. Deprecated names: `SD35_MODEL_DIR`. |
| `FASTVIDEO_TEST_TAEH3_REFERENCE_DIR` | str | unset | test | Upstream taehv checkout for the MLX TAEH3 parity test. Deprecated names: `TAEH3_REFERENCE_DIR`. |
| `FASTVIDEO_TEST_ZIMAGE_MODEL_DIR` | str | `Tongyi-MAI/Z-Image-Turbo` | test | Model for the Z-Image SSIM test. Deprecated names: `ZIMAGE_MODEL_DIR`. |
| `FASTVIDEO_TEST_ZIMAGE_MODEL_REVISION` | str | `f332072aa78be7aecdf3ee76d5c247082da564a6` | test | Hugging Face revision of the Z-Image model for its SSIM test. Deprecated names: `ZIMAGE_MODEL_REVISION`. |
Variables that FastVideo no longer reads; setting one logs a warning:
+252
View File
@@ -0,0 +1,252 @@
# Selectable GPU CI Backends
GPU CI can use the existing Slurm dispatcher, Modal, or GB200 Kubernetes Jobs
in the `vllm` namespace. GitHub and Buildkite remain the trigger and reporting
systems. `vllm` names the cluster namespace here; tests run FastVideo's existing
lane scripts rather than a vLLM inference server.
The default remains Slurm. The existing `.buildkite/pipeline.yml`, private
Slurm uploader, and dormant Modal launchers are preserved. Deploying the new
trusted uploader is an operator step; merging these files does not change the
live Buildkite pipelines or create cluster resources. See
[CI/CD Architecture](ci_architecture.md) for the existing installation.
## Execution and Limits
```text
GitHub PR / slash command / schedule
-> trusted Buildkite bootstrap
-> slurm: existing validated graph and private dispatcher
-> modal or vllm: one trusted suite coordinator
-> shared PR admission and GPU reservations
-> isolated GPU worker per existing lane
-> lane results, logs, and backend-specific suite status
```
All new Modal and `vllm` coordinators share one admission database. It enforces:
- At most two distinct PRs with admitted GPU work.
- At most four reserved GPUs per PR across its concurrent builds and lanes.
- At most eight reserved GPUs across both backends combined.
- Additional PRs wait without creating GPU workers.
A PR is identified by repository and PR number, so Fastcheck, merge builds,
manual reruns, and separate attempts share its allowance. Its slot remains
occupied between lanes until all admitted builds for that PR finish. A
non-PR build, including a schedule on `main`, consumes its own slot and the
same GPU budget. The preserved Slurm dispatcher has its existing independent
limits; this new admission database does not control legacy Slurm jobs.
Lanes retain their existing one-, two-, or four-GPU requirements. Four-GPU
SSIM and training lanes wait for that PR's other lanes to release capacity.
Selected integration lanes wait for the golden gate and are skipped if it
fails. CPU-only GitHub checks are unaffected. Pending admission is bounded by
`queue_timeout_seconds`, initially six hours. The coordinator's Buildkite
command timeout is eight hours, including admission and lane waits after the
command starts; time waiting for a Buildkite agent is outside that timeout.
Reservations persist before a worker is created. Cancellation releases them
only after worker termination is confirmed. A lost coordinator, uncertain
create response, or unavailable backend retains its reservations until
recovery confirms cleanup. There is no heartbeat expiry that silently frees
GPUs while an old worker could still be running.
## Operator Installation
Use an operator-reviewed immutable checkout under
`/opt/fastvideo-gpu-ci/source`. Install the supplied `scripts/gpu_ci/run` and
`scripts/gpu_ci/upload` wrappers as `/opt/fastvideo-gpu-ci/run` and
`/opt/fastvideo-gpu-ci/upload`. They invoke the reviewed Python entrypoint
with isolated import mode. Install the supplied files instead of generating
shell wrappers from build metadata:
```bash
install -m 755 /opt/fastvideo-gpu-ci/source/scripts/gpu_ci/run /opt/fastvideo-gpu-ci/run
install -m 755 /opt/fastvideo-gpu-ci/source/scripts/gpu_ci/upload /opt/fastvideo-gpu-ci/upload
```
The controller needs Python 3.10 or newer, PyYAML,
the Buildkite agent, and `kubectl`; Modal additionally needs the reviewed
Modal SDK and a controller-side Modal credential.
Copy [the configuration example](../../scripts/gpu_ci/config.example.json)
to `/etc/fastvideo-gpu-ci.json`. Keep the installation and configuration
operator-owned and unwritable by workers. Configure the actual legacy
`slurm_uploader` command, replace each enabled backend's image placeholder
with a reviewed registry digest, and leave `default_backend` as `slurm`
during canaries. The checked-in placeholders deliberately cannot start jobs.
The permanent dispatcher must reach the Kubernetes API independently of a
developer laptop, SSH tunnel, or Tailscale session. The existing CPU development
pod is useful for investigating access, but is not a production CI service.
Use a dedicated controller identity and a separate `gpu-ci-dispatch` Buildkite
queue. The trusted bootstrap also needs access to the existing Slurm uploader
while that backend remains enabled.
Run all coordinators on **one host**, with `state_path` and `artifacts_dir` on
its persistent local disk. The implementation uses SQLite transactions and
local file locks. Do not place the database or locks on Lustre/NFS, run
independent database copies, or scale controller replicas across nodes. Those
configurations would invalidate the global limits. Multiple Buildkite agent
processes on the same host can share the installation and ledger; enough
agents are needed for concurrent suite coordinators and queued work.
Configure agent-owned hooks to skip repository checkout and reject commands
other than the trusted uploader and coordinator. Disable repository hooks
and plugins on this queue. Do not use a PR checkout to load `scripts/gpu_ci`,
its configuration, or its worker entrypoint. Build metadata is validated by
the dispatcher; arbitrary environment variables are not forwarded into GPU
workers. Buildkite, Kubernetes, and Modal control-plane credentials stay on
the dispatcher.
For Kubernetes, provision namespace-scoped permissions to create/get/delete
Jobs and read Pods and their logs. Worker Pods disable service-account token
mounting and request the exact GPU count on ARM64 GB200 nodes. The image must
include the expected `/opt/venv` runtime, CUDA/SM100 support, and the reviewed
FA4 dependencies. Use a separate AMD64 image digest for Modal. The Modal
profile preserves H100 for encoder, custom-kernel, and VSA lanes, and L40S
for the remaining lanes. Its reviewed image must support both SM89 and
SM90a. Attention settings are selected per backend and lane to preserve the
existing Modal and GB200 FA4 profiles; do not replace them with one global
attention override. Validate the images against their respective hardware
before enabling merge gates.
Optional Kubernetes configuration includes `context`, `hf_secret`,
`hf_secret_key`, `cache_pvc`, `cache_subpath`, `artifacts_pvc`, and
`artifacts_subpath`. Only provide a read-only Hugging Face credential when
private/gated downloads require it. PR workers are untrusted and can access
any credential supplied to them, so never provide a reference-publication
or other write-capable token. Prefer a pre-populated read-only cache.
Use dedicated CI PVCs and relative CI subpaths. The personal
`lustre-pvc-vllm` and `nfs-pvc-vllm` claims are rejected. The cache is mounted
read-only; mutable references and locks stay inside the worker. The artifact
init container prepares each Job's dedicated artifact subdirectory before
the worker mounts it. An
operator quota or admission policy scoped to CI can provide a second GPU
ceiling; do not apply an eight-GPU quota to the shared `vllm` namespace if it
would also cap other users' work.
## Wire Every Buildkite Entry Pipeline
Set each pipeline's operator-owned bootstrap command to
`/opt/fastvideo-gpu-ci/upload`. Apply this to all three entry pipelines:
| Pipeline | Trigger and scope |
|---|---|
| `pr-fastcheck` | Automatic PR webhook; `TEST_SCOPE=fastcheck` or unset. |
| `ci` | Existing API triggers for Fastcheck, full, merge, direct, and scheduled SSIM. Keep its incoming PR webhook disabled to avoid duplicate builds. |
| `fastvideo-performance-lane` | Existing schedule; `TEST_SCOPE=direct`, `TEST_TYPE=performance`. |
The wrapper resolves `CI_GPU_BACKEND` from the build environment, falling
back to `default_backend` in the trusted configuration. Allowed values are
exactly `slurm`, `modal`, and `vllm`; unknown values fail. For Slurm, upload
delegates to the unchanged private uploader. For Modal or `vllm`, it uploads
one fixed `/opt/fastvideo-gpu-ci/run` command on the dedicated queue, with
the selected backend and scope pinned in step environment.
Set `CI_GPU_BACKEND=vllm` in the Buildkite build environment for a rack canary,
or `modal` for a Modal canary. The existing API build payload can carry the
same string in its `env` object. Automatic PR webhook builds inherit the
operator's configured default unless pipeline/build configuration explicitly
overrides it. Do not put backend selection inside a PR-controlled command.
Keep the existing exact `BUILDKITE_COMMIT`, repository, PR identity, and
`TEST_SCOPE` metadata. Direct runs also need an allowlisted `TEST_TYPE`.
Merge runs need the trusted base-branch planner's `MERGE_TEST_PLAN`,
`MERGE_GOLDEN_TESTS`, and `MERGE_SSIM_TESTS`. Full/direct/scheduled quality
runs keep their complete matrices. The worker fetches and verifies the
immutable commit before installing dependencies or invoking a lane script.
The new Modal adapter uses bounded one-to-four-GPU sandboxes and the same
lane scripts as Kubernetes. It does not reactivate `pr_test.sh` or the old
Modal SSIM fan-out, which cannot enforce this shared four-GPU-per-PR budget.
The legacy files remain available for their existing manual workflows.
## Statuses and Default-Backend Cutover
The selected adapter reports separate suite contexts:
| Scope | GitHub context |
|---|---|
| Fastcheck | `gpu-ci/<backend>/fastcheck-passed` |
| Merge or explicit full suite | `gpu-ci/<backend>/full-suite-passed` |
| Direct lane | `gpu-ci/<backend>/direct-test-completed` |
| Scheduled SSIM | `gpu-ci/<backend>/scheduled-ssim-passed` |
The repository variable `CI_GPU_BACKEND` controls which new backend can
publish the existing required `fastcheck-passed` and `full-suite-passed`
contexts. Empty or `slurm` preserves the existing status behavior. `modal`
or `vllm` enables `ci-gpu-backend-status.yml`, which reads the latest statuses
for that one backend and mirrors both required contexts. A missing result
becomes pending; failure and error remain failures. It never combines one
backend's Fastcheck with another backend's full-suite result. Late status
events re-read current state instead of replaying stale event payloads.
Direct tests are diagnostic and do not promote a whole suite on the new
backends. Rerun the matching Fastcheck or merge/full suite to clear its gate.
Per-build tests on the other new backend remain separate diagnostics. The
legacy direct-test aggregation workflow is disabled while a new backend is
selected. These workflows retain the existing trust assumption that only
authorized status-writing integrations can publish CI status contexts.
For production cutover, drain existing Slurm builds: its preserved pipeline
still emits canonical status contexts. The new uploader rejects a Slurm
override when `default_backend` is `modal` or `vllm`, preventing later legacy
builds from overwriting the promoted backend's checks. Ensure no old bootstrap
bypasses the new uploader. Synchronize the operator `default_backend` and
the GitHub repository variable, then clear or rerun required checks for all
open PRs. Old green contexts do not become new-backend validation merely
because a setting changed. Run both Fastcheck and the merge/full suite on
the promoted backend before allowing merge. Apply the same drain and rerun
procedure when rolling back to Slurm.
## Validation and Recovery
Inspect the rendered opt-in pipeline without submitting a build:
```bash
CI_GPU_BACKEND=vllm TEST_SCOPE=fastcheck \
/opt/fastvideo-gpu-ci/venv/bin/python -I \
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py render \
--config /etc/fastvideo-gpu-ci.json
```
Validate a one-GPU lane, then a two-/four-GPU lane and the complete Fastcheck
suite. Exercise two PRs plus a third waiter, concurrent builds of the same
PR, cancellation, retries, and coordinator restart. Confirm observed GPU
reservations never exceed two PRs, four per PR, or eight in total. A GPU
reservation includes a pending worker, so unavailable nodes cannot cause
the controller to submit more work than its allowance.
Run SSIM, training, and performance canaries separately. References must
match the effective GPU/runtime/attention backend; do not silently reuse
L40S performance results as GB200 baselines or reseed references as part of
routine CI. Workers keep W&B offline and disable reference publication.
Inspect the persistent ledger and recover an abandoned attempt with:
```bash
/opt/fastvideo-gpu-ci/venv/bin/python -I \
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py status \
--config /etc/fastvideo-gpu-ci.json
/opt/fastvideo-gpu-ci/venv/bin/python -I \
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py recover \
--config /etc/fastvideo-gpu-ci.json --build-id BUILD_ID.JOB_ID
```
Use the exact ledger ID from `status`; each Buildkite retry has a distinct
job ID. Recovery refuses a live coordinator, persists cancellation, stops
owned resources, and releases reservations only after confirming termination.
If creation or cleanup is ambiguous, investigate the recorded handle on its
backend and retain the reservation until the outcome is known. Do not delete
the database or manually zero counters to unblock the queue.
The dispatcher uploads controller logs, per-lane numeric results, request
metadata, and the suite summary to Buildkite. These are the sources for its
exit status. Generated videos and JUnit files stay in the worker unless a
dedicated Kubernetes artifact PVC is configured; automatic publication of
those worker files and Modal worker artifacts is not implemented. This is
a deployment limitation to account for before replacing existing artifact
review workflows.
+1 -1
View File
@@ -112,7 +112,7 @@ per-metric policy with direction, percent threshold, absolute threshold, and a
`test_inference_performance.py` temporarily sets `FASTVIDEO_STAGE_LOGGING=1`
while it runs so pipeline stage execution times are available in
`generate(...).logging_info`. Stage logs use pipeline-unique keys such as
`generate_video(...).logging_info`. Stage logs use pipeline-unique keys such as
`prompt_encoding_stage` so duplicate stage classes do not collide. For
`PipelineStage` entries, shared component stage bases emit a stable
`component_metric`: text encoding stages map to `text_encoder_time_s`,
+125 -154
View File
@@ -2,158 +2,136 @@ status_definitions:
kept: "Public field remains on a public adapter surface with the same meaning."
moved: "Public field remains supported but normalizes into a different nested path."
preset_owned: "Public field remains supported only through a model/preset-specific surface."
compatibility_only: "Public field that an adapter or an open mapping accepts outside the typed fields of the canonical schema."
compatibility_only: "Legacy public field remains adapter-only during migration and is not part of the canonical typed schema."
private_only: "Field should only be handled by private adapters and is not a public FastVideo compatibility promise."
internal_only: "Field is runtime/config plumbing that model code, config resolution, or the runtime fills; it is not a public input."
unsupported: "Typed config field that no runtime code reads; resolution rejects a value."
internal_only: "Field is runtime/config plumbing and should not be part of the new public typed inference API."
surfaces:
generator_config:
kept:
- model_path
- mode
- revision
- trust_remote_code
- engine.num_gpus
- engine.execution_backend
- engine.parallelism.tp_size
- engine.parallelism.sp_size
- engine.parallelism.hsdp_replicate_dim
- engine.parallelism.hsdp_shard_dim
- engine.parallelism.dist_timeout
- engine.offload.dit
- engine.offload.dit_layerwise
- engine.offload.text_encoder
- engine.offload.image_encoder
- engine.offload.vae
- engine.offload.pin_cpu_memory
- engine.compile.enabled
- engine.compile.backend
- engine.compile.fullgraph
- engine.compile.mode
- engine.compile.dynamic
- engine.compile.extras
- engine.enable_stage_verification
- engine.use_fsdp_inference
- engine.disable_autocast
- engine.attention.nvfp4_fa4
- pipeline.components.lora_path
- pipeline.components.lora_strength
- pipeline.output_type
- engine.parallelism.master_port
- engine.offload.lazy_module_load
- engine.compile.text_encoder_enabled
- engine.compile.vae_enabled
- engine.compile.audio_vae_enabled
- engine.compile.regional
- engine.compile.dit_kwargs
- engine.compile.text_encoder_kwargs
- engine.compile.vae_kwargs
- engine.compile.audio_vae_kwargs
- engine.attention.backend
- engine.attention.vsa_sparsity
- engine.attention.vsa_tile_size
- engine.attention.moba_config_path
- engine.precision.dit
- engine.precision.vae
- engine.precision.vae_decode
- engine.precision.image_encoder
- engine.precision.text_encoders
- engine.quantization.text_encoder_quant
- engine.quantization.transformer_quant
- pipeline.workload_type
- pipeline.components.config_root
- pipeline.components.pipeline_config_path
- pipeline.components.text_encoder_weights
- pipeline.components.transformer_weights
- pipeline.components.transformer_2_weights
- pipeline.components.upsampler_weights
- pipeline.components.lora_nickname
- pipeline.components.lora_target_modules
- pipeline.components.override_pipeline_cls_name
- pipeline.components.override_transformer_cls_name
- pipeline.vae_tiling
- pipeline.vae_sp
- pipeline.flow_shift
- pipeline.embedded_cfg_scale
- pipeline.dmd_denoising_steps
- pipeline.boundary_ratio
- pipeline.model.generic.dit
- pipeline.model.generic.vae
- pipeline.model.ltx2.dit
- pipeline.model.ltx2.vae
- pipeline.model.minimax_h3.dit
- pipeline.model.minimax_h3.vae
- pipeline.model.longcat.dit
- pipeline.model.longcat.vae
unsupported:
- pipeline.preset
- pipeline.preset_version
- pipeline.components.vae_weights
fastvideo_args:
moved:
pipeline.preset_overrides:
target: generator.pipeline.model.ltx2.refine
note: "Only the refine mapping applies: resolution copies pipeline.preset_overrides.refine into the pipeline.model.ltx2.refine fields of the same names when the model is LTX-2. Other keys have no effect."
model_path: generator.model_path
workload_type: generator.pipeline.workload_type
distributed_executor_backend: generator.engine.execution_backend
trust_remote_code: generator.trust_remote_code
revision: generator.revision
num_gpus: generator.engine.num_gpus
tp_size: generator.engine.parallelism.tp_size
sp_size: generator.engine.parallelism.sp_size
hsdp_replicate_dim: generator.engine.parallelism.hsdp_replicate_dim
hsdp_shard_dim: generator.engine.parallelism.hsdp_shard_dim
dist_timeout: generator.engine.parallelism.dist_timeout
lora_path: generator.pipeline.components.lora_path
lora_nickname: generator.pipeline.components.lora_nickname
lora_strength: generator.pipeline.components.lora_strength
dit_cpu_offload: generator.engine.offload.dit
use_fsdp_inference: generator.engine.use_fsdp_inference
dit_layerwise_offload: generator.engine.offload.dit_layerwise
text_encoder_cpu_offload: generator.engine.offload.text_encoder
image_encoder_cpu_offload: generator.engine.offload.image_encoder
vae_cpu_offload: generator.engine.offload.vae
pin_cpu_memory: generator.engine.offload.pin_cpu_memory
lazy_module_load: generator.engine.offload.lazy_module_load
enable_torch_compile: generator.engine.compile.enabled
enable_torch_compile_text_encoder: generator.engine.compile.text_encoder_enabled
enable_torch_compile_vae: generator.engine.compile.vae_enabled
enable_torch_compile_audio_vae: generator.engine.compile.audio_vae_enabled
torch_compile_kwargs: generator.engine.compile.backend,fullgraph,mode,dynamic,extras
torch_compile_kwargs_dit: generator.engine.compile.dit_kwargs
torch_compile_kwargs_text_encoder: generator.engine.compile.text_encoder_kwargs
torch_compile_kwargs_vae: generator.engine.compile.vae_kwargs
torch_compile_kwargs_audio_vae: generator.engine.compile.audio_vae_kwargs
transformer_quant: generator.engine.quantization.transformer_quant
disable_autocast: generator.engine.disable_autocast
enable_stage_verification: generator.engine.enable_stage_verification
prompt_txt: request.inputs.prompt_path
override_text_encoder_safetensors: generator.pipeline.components.text_encoder_weights
override_text_encoder_quant: generator.engine.quantization.text_encoder_quant
transformer_quant: generator.engine.quantization.transformer_quant
override_transformer_cls_name: generator.pipeline.components.override_transformer_cls_name
init_weights_from_safetensors: generator.pipeline.components.transformer_weights
init_weights_from_safetensors_2: generator.pipeline.components.transformer_2_weights
override_pipeline_cls_name: generator.pipeline.components.override_pipeline_cls_name
boundary_ratio: request.sampling.boundary_ratio
ltx2_vae_tiling: generator.pipeline.vae_tiling
refine_enabled: generator.pipeline.preset_overrides.refine.enabled
refine_upsampler_path: generator.pipeline.components.upsampler_weights
refine_lora_path: generator.pipeline.components.lora_path
refine_num_inference_steps: request.stage_overrides.refine.num_inference_steps
refine_guidance_scale: request.stage_overrides.refine.guidance_scale
refine_add_noise: generator.pipeline.preset_overrides.refine.add_noise
ltx2_refine_enabled: generator.pipeline.preset_overrides.refine.enabled
ltx2_refine_upsampler_path: generator.pipeline.components.upsampler_weights
ltx2_refine_lora_path: generator.pipeline.components.lora_path
ltx2_refine_num_inference_steps: request.stage_overrides.refine.num_inference_steps
ltx2_refine_guidance_scale: request.stage_overrides.refine.guidance_scale
ltx2_refine_add_noise: generator.pipeline.preset_overrides.refine.add_noise
preset_owned:
- pipeline.model.ltx2.vae_spatial_tile_size_in_pixels
- pipeline.model.ltx2.vae_spatial_tile_overlap_in_pixels
- pipeline.model.ltx2.vae_temporal_tile_size_in_frames
- pipeline.model.ltx2.vae_temporal_tile_overlap_in_frames
- pipeline.model.ltx2.initial_latent_path
- pipeline.model.ltx2.audio_latent_path
- pipeline.model.ltx2.legacy_native_noise_order
- pipeline.model.ltx2.use_distilled_sigmas
- pipeline.model.ltx2.refine.enabled
- pipeline.model.ltx2.refine.num_inference_steps
- pipeline.model.ltx2.refine.guidance_scale
- pipeline.model.ltx2.refine.add_noise
- pipeline.model.ltx2.refine.image_crf
- pipeline.model.ltx2.refine.video_position_offset_sec
- pipeline.model.ltx2.refine.transformer_path
- pipeline.model.ltx2.refine.lora_path
- pipeline.model.ltx2.refine.noise_path
- pipeline.model.ltx2.refine.audio_noise_path
- pipeline.model.minimax_h3.sequential_load
- pipeline.model.minimax_h3.video_decode_backend
- pipeline.model.minimax_h3.taeh3_checkpoint
- pipeline.model.minimax_h3.taeh3_chunk_size
- pipeline.model.minimax_h3.vae_parallel_decode
- pipeline.model.minimax_h3.vae_parallel_encode
- pipeline.model.minimax_h3.vae_parallel_decode_strategy
- pipeline.model.longcat.enable_bsa
- pipeline.model.longcat.bsa_sparsity
- pipeline.model.longcat.bsa_cdf_threshold
- pipeline.model.longcat.bsa_chunk_q
- pipeline.model.longcat.bsa_chunk_k
ltx2_vae_spatial_tile_size_in_pixels: generator.pipeline.preset_overrides.ltx2.vae.spatial_tile_size_in_pixels
ltx2_vae_spatial_tile_overlap_in_pixels: generator.pipeline.preset_overrides.ltx2.vae.spatial_tile_overlap_in_pixels
ltx2_vae_temporal_tile_size_in_frames: generator.pipeline.preset_overrides.ltx2.vae.temporal_tile_size_in_frames
ltx2_vae_temporal_tile_overlap_in_frames: generator.pipeline.preset_overrides.ltx2.vae.temporal_tile_overlap_in_frames
ltx2_initial_latent_path: request.extensions.ltx2.initial_latent_path
ltx2_audio_latent_path: request.extensions.ltx2.audio_latent_path
compatibility_only:
pipeline.experimental: "Open mapping for settings without a typed path: the pipeline_config source (a JSON path, a mapping, or a PipelineConfig), keys that runtime code reads by name (for example ray_runtime_env), and PipelineConfig attribute overrides for model-only fields (for example flow_shift_sr). Resolution rejects a key whose PipelineConfig attribute has a typed path."
mode: "Legacy multi-mode FastVideoArgs switch; typed inference config should not expose execution mode."
inference_mode: "Legacy boolean mirror of mode; kept only through adapters while FastVideoArgs remains."
lora_target_modules: "Legacy LoRA configuration surface pending dedicated component API."
output_type: "Legacy output formatting surface pending GenerationResult cleanup."
VSA_sparsity: "Model-specific inference optimization not yet represented in the typed public schema."
VSA_tile_size: "VSA-H3 tile geometry request; model-specific optimization not yet represented in the typed public schema."
inference_torch_compile: "Regional inference compile opt-in currently carried through PipelineSelection.experimental rather than CompileConfig."
vae_parallel_decode: "MiniMax-H3 sequence-parallel VAE decode opt-in; model-specific optimization not yet represented in the typed public schema."
h3_sequential_load: "MiniMax-H3 sequential text-encoder then DiT/VAE load; model-specific optimization not yet represented in the typed public schema."
video_decode_backend: "MiniMax-H3 video decoder selection (full VAE vs TAEH3 preview); model-specific optimization not yet represented in the typed public schema."
taeh3_checkpoint: "Optional local TAEH3 safetensors path; model-specific optimization not yet represented in the typed public schema."
taeh3_chunk_size: "TAEH3 temporal chunk length; model-specific optimization not yet represented in the typed public schema."
vae_parallel_encode: "MiniMax-H3 sequence-parallel reference VAE encode opt-in; model-specific optimization not yet represented in the typed public schema."
vae_parallel_decode_strategy: "Chunk-transport collective for vae_parallel_decode; model-specific optimization not yet represented in the typed public schema."
attention_backend: "Process-wide default attention-backend request applied per component at load time; kernel-selection knob not yet represented in the typed public schema."
moba_config_path: "Model-specific MoBA optimization surface not yet represented in the typed public schema."
master_port: "Executor/bootstrap compatibility field; not part of the canonical inference schema."
refine_transformer_path: "Generic stage-2 refine transformer override; no typed equivalent yet."
refine_noise_path: "Generic stage-2 refine noise override; no typed equivalent yet."
refine_audio_noise_path: "Generic stage-2 refine audio noise override; no typed equivalent yet."
ltx2_refine_transformer_path: "LTX-2 refine transformer carrier; no typed equivalent yet."
ltx2_refine_noise_path: "LTX-2 refine noise carrier; no typed equivalent yet."
ltx2_refine_audio_noise_path: "LTX-2 refine audio noise carrier; no typed equivalent yet."
ltx2_legacy_native_noise_order: "LTX-2 SSIM compatibility knob preserving legacy native latent noise ordering."
ltx2_use_distilled_sigmas: "LTX-2 compatibility knob gating use of distilled sigma schedule."
private_only:
ray_placement_group: "Ray deployment-only field."
ray_runtime_env: "Ray deployment-only field."
internal_only:
engine.attention.moba_config: "V-MoBA attention settings that resolution loads from engine.attention.moba_config_path."
pipeline_config: "Legacy internal carrier object."
preprocess_config: "Legacy preprocess carrier object."
moba_config: "Derived runtime config loaded from moba_config_path."
model_paths: "Runtime bookkeeping."
model_loaded: "Runtime bookkeeping."
pipeline_config_base:
moved:
model_path: generator.model_path
pipeline_config_path: generator.pipeline.components.pipeline_config_path
embedded_cfg_scale: generator.pipeline.embedded_cfg_scale
flow_shift: generator.pipeline.flow_shift
disable_autocast: generator.engine.disable_autocast
vae_tiling: generator.pipeline.vae_tiling
vae_sp: generator.pipeline.vae_sp
dmd_denoising_steps: generator.pipeline.dmd_denoising_steps
boundary_ratio: generator.pipeline.boundary_ratio
dit_precision: generator.engine.precision.dit
vae_precision: generator.engine.precision.vae
vae_decode_precision: generator.engine.precision.vae_decode
image_encoder_precision: generator.engine.precision.image_encoder
text_encoder_precisions: generator.engine.precision.text_encoders
preset_owned:
flow_shift_sr: generator.pipeline.experimental.flow_shift_sr
is_causal: generator.pipeline.experimental.is_causal
ti2v_task: generator.pipeline.experimental.ti2v_task
lucy_edit_task: generator.pipeline.experimental.lucy_edit_task
embedded_cfg_scale: generator.pipeline.preset_overrides.embedded_cfg_scale
flow_shift: generator.pipeline.preset_overrides.flow_shift
flow_shift_sr: generator.pipeline.preset_overrides.flow_shift_sr
is_causal: generator.pipeline.preset_overrides.is_causal
vae_tiling: generator.pipeline.preset_overrides.vae_tiling
vae_sp: generator.pipeline.preset_overrides.vae_sp
dmd_denoising_steps: generator.pipeline.preset_overrides.dmd_denoising_steps
ti2v_task: generator.pipeline.preset_overrides.ti2v_task
lucy_edit_task: generator.pipeline.preset_overrides.lucy_edit_task
boundary_ratio: generator.pipeline.preset_overrides.boundary_ratio
compatibility_only:
model_path: "Redundant with generator.model_path."
disable_autocast: "Duplicated by generator.engine.disable_autocast during migration."
dit_precision: "Precision override pending dedicated typed component precision design."
upsampler_precision: "Precision override pending dedicated typed component precision design."
vae_precision: "Precision override pending dedicated typed component precision design."
vae_decode_precision: "Decode-only precision override pending dedicated typed component precision design."
image_encoder_precision: "Precision override pending dedicated typed component precision design."
image_encoder_precisions: "Precision overrides pending dedicated typed component precision design."
text_encoder_precisions: "Precision override pending dedicated typed component precision design."
internal_only:
dit_config: "Legacy internal component config object."
upsampler_config: "Legacy internal component config object."
@@ -166,22 +144,6 @@ surfaces:
scheduler_step_in_fp32: "Runtime scheduler precision toggle; not part of the public typed inference API."
pipeline_config_extensions:
moved:
enable_bsa:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
target: generator.pipeline.model.longcat.enable_bsa
bsa_sparsity:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
target: generator.pipeline.model.longcat.bsa_sparsity
bsa_cdf_threshold:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
target: generator.pipeline.model.longcat.bsa_cdf_threshold
bsa_chunk_q:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
target: generator.pipeline.model.longcat.bsa_chunk_q
bsa_chunk_k:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
target: generator.pipeline.model.longcat.bsa_chunk_k
preset_owned:
flux2_text_encoder_type:
sources:
@@ -362,8 +324,18 @@ surfaces:
- fastvideo.configs.pipelines.wan.WanI2V720PConfig
- fastvideo.configs.pipelines.wan.WanT2V480PConfig
- fastvideo.configs.pipelines.wan.WanT2V720PConfig
bsa_cdf_threshold:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
bsa_chunk_k:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
bsa_chunk_q:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
bsa_params:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
bsa_sparsity:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
enable_bsa:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
enable_kv_cache:
sources: [fastvideo.configs.pipelines.longcat.LongCatT2V480PConfig, fastvideo.configs.pipelines.longcat.LongCatT2V704PConfig]
enhance_hf:
@@ -548,7 +520,6 @@ surfaces:
cfg_truncation: request.sampling.cfg_truncation
guidance_rescale: request.sampling.guidance_rescale
use_embedded_guidance: request.sampling.use_embedded_guidance
embedded_cfg_scale: request.sampling.embedded_cfg_scale
true_cfg_scale: request.sampling.true_cfg_scale
boundary_ratio: request.sampling.boundary_ratio
sigmas: request.sampling.sigmas
+12 -11
View File
@@ -28,15 +28,19 @@ Minimal usage (from `examples/inference/basic/basic.py`):
```python
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
model_id = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" # or official_weights/<model_name>/
generator = VideoGenerator.from_pretrained(model_id, {"engine": {"num_gpus": 1}})
generator = VideoGenerator.from_pretrained(model_id, num_gpus=1)
video = generator.generate({
"prompt": "A vibrant city street at sunset.",
"sampling": {"num_frames": 45},
"output": {"output_path": "video_samples", "save_video": True},
})
sampling = SamplingParam.from_pretrained(model_id)
sampling.num_frames = 45
video = generator.generate_video(
"A vibrant city street at sunset.",
sampling_param=sampling,
output_path="video_samples",
save_video=True,
)
```
## Configuration system
@@ -70,11 +74,8 @@ not override checkpoint manifests, user pipeline overrides, or component
precision settings. HF IDs, local checkpoints, and old config imports retain
their existing resolution behavior, including first-match detector ordering.
`ResolvedGeneratorConfig` (in `fastvideo/api/resolution.py`) provides runtime
settings and is passed into pipeline construction and stages as `resolved_config`.
`resolve_inference_config` (in `fastvideo/api/inference_resolution.py`) builds it
from the typed config in `fastvideo/api/schema.py` and attaches the model's
frozen `PipelineConfig` as `resolved_config.pipeline_config`.
`FastVideoArgs` (in `fastvideo/fastvideo_args.py`) provides runtime settings and
is passed into pipeline construction and stages.
## Weights and Diffusers format
+10 -11
View File
@@ -32,7 +32,7 @@ from fastvideo.api import (
| Surface | Availability | Notes |
| --- | --- | --- |
| `VideoGenerator.from_pretrained(model_path, config)` | Today | `config` is a nested `GeneratorConfig` mapping without `model_path`; no flat keywords |
| `VideoGenerator.from_pretrained(model_path, **typed_kwargs)` | Today | `typed_kwargs` is a stable subset from `GeneratorConfig` — no flat legacy LTX-2 kwargs (guaranteed after PR 6) |
| `VideoGenerator.generate(request: GenerationRequest) -> GenerationResult` | Today | Aggregated; Dynamo wraps in `asyncio.to_thread` under `asyncio.Lock` |
| `VideoGenerator.generate_async(request) -> AsyncGenerator[VideoEvent, None]` | **PR 7.10** | Canonical execution substrate; sync wrapper reroutes through this |
| `VideoGenerator.default_health_check_request() -> GenerationRequest` | **PR 7.10** | 256x256 / 8 frames / 1 step; lets Dynamo build its health payload without knowing any FastVideo internals |
@@ -225,7 +225,7 @@ async def init_video_generation(runtime, config, shutdown_endpoints):
from fastvideo.api import config_to_dict
server_args, dynamo_args = config.server_args, config.dynamo_args
generator = VideoGenerator.from_config(build_generator_config(server_args))
generator = VideoGenerator.from_pretrained(**config.fastvideo_kwargs())
dump_config(dynamo_args.dump_config_to, config)
@@ -262,8 +262,7 @@ this adapter can build the config purely from the public typed schema:
def build_generator_config(args) -> "GeneratorConfig":
from fastvideo.api import (
CompileConfig, ComponentConfig, EngineConfig, GeneratorConfig,
LTX2Options, LTX2RefineOptions, OffloadConfig, ParallelismConfig,
PipelineSelection,
OffloadConfig, ParallelismConfig, PipelineSelection,
)
return GeneratorConfig(
model_path=args.model_path,
@@ -276,8 +275,10 @@ def build_generator_config(args) -> "GeneratorConfig":
pipeline=PipelineSelection(
workload_type=args.workload or "t2v",
preset=args.preset, # e.g. "ltx2_two_stage"
components=ComponentConfig(upsampler_weights=args.refine_upsampler),
model=LTX2Options(refine=LTX2RefineOptions(lora_path=args.refine_lora)),
components=ComponentConfig(
upsampler_weights=args.refine_upsampler,
lora_path=args.refine_lora,
),
),
)
```
@@ -309,11 +310,8 @@ re-chase FastVideo drift:
2. `ContinuationState.payload` is JSON-serializable or references
opaque blob ids. Dynamo can round-trip it through RPC without
special-casing torch tensors.
3. `VideoGenerator.from_pretrained(model_path, config)` takes a typed
`GeneratorConfig` or its nested mapping; any flat keyword raises
`TypeError` that points to the nested config.
`VideoGenerator.from_config(...)` takes the same settings with
`model_path` inside.
3. `VideoGenerator.from_pretrained` accepts a typed `GeneratorConfig`;
legacy flat kwargs are compatibility-only and deprecate in PR 13.
4. `generate_async` (PR 7.10+) emits events in order
`Progress* → Partial* → Final`; the final event always has exactly
one occurrence per request.
@@ -331,6 +329,7 @@ at FastVideo's CI — before the Dynamo-side integration even knows.
* Anything under `fastvideo.pipelines.*` directly (pipelines are
internal; presets identify them by name on
`PipelineSelection.preset`).
* `fastvideo.fastvideo_args.FastVideoArgs` (legacy compat type).
* `fastvideo.api.compat.*` private helpers
(`_validate_continuation_state` etc.) — the public boundary is
`VideoGenerator` + `fastvideo.api`.
@@ -17,7 +17,7 @@ xDiT vendor. Do not install xDiT for this path.
| Goal | How | Use two Sparks? |
|---|---|---|
| Two independent videos at once | One process per box, `engine.num_gpus: 1` | Throughput only. Each clip still takes the 1-GPU time for that size. |
| Two independent videos at once | One process per box, `num_gpus=1` | Throughput only. Each clip still takes the 1-GPU time for that size. |
| One clip, faster | Ray + `sp_size=2` + parallel VAE | **Yes.** One 768×1344×124 recipe was 292 s vs 374 s on one GB10. |
| One clip, longer | Same, more frames | **Yes.** 345 frames (~14.4 s at 24 fps) finished in 587 s at 768×1344. |
@@ -35,7 +35,7 @@ over ~21 GB/s RoCE.
QSFP; do not download 100+ GB twice over Wi-Fi.
- Ray in the FastVideo venv (`uv pip install ray` if it is not already there).
Each Spark has **one** GPU. `engine.num_gpus: 2` therefore means two nodes, which is
Each Spark has **one** GPU. `num_gpus=2` therefore means two nodes, which is
why the executor must be Ray (`mp` only works inside one process tree).
## 1. Put IPv4 on the QSFP NICs
@@ -197,8 +197,8 @@ sm_100a VSA kernel is not on this chip, so denoise is slower than a GB200
| NCCL hangs or uses Wi-Fi | `source spark_pair_env.sh`. Confirm `NCCL_SOCKET_IFNAME` is the QSFP NIC. |
| Gloo `connectFullMesh` / `remote=[127.0.0.1]` | Two 1-GPU nodes must not use loopback as the Gloo store. Source `spark_pair_env.sh` so `GLOO_SOCKET_IFNAME` is the QSFP NIC on **each** box. FastVideo no longer copies that NIC name from the driver onto workers. |
| Second `generate()` crashes `NoneType.parameters` | Sequential load used to drop the text encoder without reloading it. This branch reloads Qwen for later requests so `--warmup --repeats N` works. |
| OOM / `earlyoom` prefers Python | Lazy module load must stay on (do not pass `--no-lazy-module-load` to `basic_fasth3.py` or set `engine.offload.lazy_module_load: false`). Peak GPU during 345-frame denoise is ~90 GiB/node. |
| `engine.num_gpus: 2` on one Spark | Each Spark has one GPU. Use Ray across two nodes, or `engine.num_gpus: 1` on one box. |
| OOM / `earlyoom` prefers Python | Lazy module load must stay on (do not pass `--no-lazy-module-load`). Peak GPU during 345-frame denoise is ~90 GiB/node. |
| `num_gpus=2` on one Spark | Each Spark has one GPU. Use Ray across two nodes, or `num_gpus=1` on one box. |
## What we are not claiming
@@ -62,12 +62,10 @@ nothing to set. If you run a model that still defaults to an fp32 decode, set th
decode-only override yourself:
```python
from fastvideo import VideoGenerator
from fastvideo.configs.pipelines.base import PipelineConfig
generator = VideoGenerator.from_config({
"model_path": model_id,
"engine": {"precision": {"vae_decode": "bf16"}}, # decode-only; leaves encode precision alone
})
pipeline_config = PipelineConfig.from_pretrained(model_id)
pipeline_config.vae_decode_precision = "bf16" # decode-only; leaves encode precision alone
```
Decode is output-only, so lowering its precision is safe. (Encode seeds the
@@ -168,9 +166,8 @@ is power-cycled. To avoid it:
encoder is still resident, the process is a typical `earlyoom` kill (Python is
preferred). On unified memory, `lazy_module_load` auto-enables and owns that
split (encoder, then DiT, then VAE; DiT can drop before decode). Sequential
load is the H3-only fallback when lazy is off; do not set
`engine.offload.lazy_module_load` to false here (`--no-lazy-module-load` in
`basic_fasth3.py` and `basic_minimax_h3_t2v.py`). Geometry scalars come from checkpoint
load is the H3-only fallback when lazy is off; do not pass
`--no-lazy-module-load` here. Geometry scalars come from checkpoint
`config.json`, not live weights. See [Offloading](../../inference/offloading.md).
- **FastH3 TAEH3** (`--video-decode-backend taeh3`) is an opt-in preview decoder.
T2VA never materializes the 9.7 GiB video VAE (DiT still loads after Qwen via
@@ -204,7 +201,7 @@ A few things that surprise people on this box (beyond the memory notes above):
build recent enough to include its `transformers`-compatibility handling before
running it.
- **MiniMax H3 worker init can look healthy and still die on the first generate**
if deferred loading is off (`engine.offload.lazy_module_load: false` and sequential also off)
if deferred loading is off (`--no-lazy-module-load` and sequential also off)
and encoder, VAE, and DiT load together. On GB10 the log should show
`lazy_module_load owns deferral` (or, if lazy is off, sequential
`Released MiniMax-H3 text encoder after conditioning` before
+4 -4
View File
@@ -210,7 +210,7 @@ from fastvideo.pipelines.stages import (
InputValidationStage, CLIPTextEncodingStage, TimestepPreparationStage,
LatentPreparationStage, DenoisingStage, DecodingStage
)
from fastvideo.api.resolution import ResolvedGeneratorConfig
from fastvideo.fastvideo_args import FastVideoArgs
from fastvideo.pipelines.pipeline_batch_info import ForwardBatch
import torch
@@ -226,11 +226,11 @@ class MyCustomPipeline(ComposedPipelineBase):
def required_config_modules(self) -> List[str]:
return self._required_config_modules
def initialize_pipeline(self, resolved_config: ResolvedGeneratorConfig):
def initialize_pipeline(self, fastvideo_args: FastVideoArgs):
"""Initialize pipeline-specific components."""
pass
def create_pipeline_stages(self, resolved_config: ResolvedGeneratorConfig):
def create_pipeline_stages(self, fastvideo_args: FastVideoArgs):
"""Set up pipeline stages with proper dependency injection."""
self.add_stage(
stage_name="input_validation_stage",
@@ -294,7 +294,7 @@ class MyCustomStage(PipelineStage):
self.custom_module = custom_module
self.other_param = other_param
def forward(self, batch: ForwardBatch, resolved_config: ResolvedGeneratorConfig) -> ForwardBatch:
def forward(self, batch: ForwardBatch, fastvideo_args: FastVideoArgs) -> ForwardBatch:
# Access input data
input_data = batch.some_attribute
+38 -51
View File
@@ -103,10 +103,6 @@ PipelineConfig (fastvideo/configs/pipelines/base.py)
- Precision settings: `dit_precision`, `vae_precision`,
`text_encoder_precisions`.
These generation and precision attributes hold the model defaults. Resolution
copies them into the typed fields of the resolved config (`pipeline.flow_shift`,
`engine.precision.dit`, ...), and runtime code reads the typed fields.
Model-specific subclasses override defaults. For example,
`WanT2V480PConfig` sets `flow_shift=3.0` and uses `WanVideoConfig` as
its DiT config.
@@ -131,11 +127,9 @@ Concrete hierarchy: `DiTConfig` → `DiTArchConfig`, `VAEConfig` →
- `PipelineConfig.from_pretrained(model_path)` — resolves config class
via `get_pipeline_config_cls_from_name()`, instantiates with defaults.
- `PipelineConfig.from_source(model_path, source)` — resolves the registry
class of `model_path`, then updates it from `source`: a JSON path loaded
via `load_from_json()`, a mapping of field values applied via
`update_pipeline_config()`, or a `PipelineConfig` that replaces the
registry instance.
- `PipelineConfig.from_kwargs(kwargs)` — resolves class, optionally loads
JSON via `load_from_json()`, then applies CLI overrides via
`update_config_from_dict()`.
- `dump_to_json()` / `load_from_json()` — JSON persistence. Callable
fields and `arch_config` are excluded from dumps.
@@ -153,7 +147,7 @@ sp = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
### ComponentLoader (`fastvideo/models/loader/component_loader.py`)
Abstract base with a `load(model_path, resolved_config)` method.
Abstract base with a `load(model_path, fastvideo_args)` method.
`ComponentLoader.for_module_type(module_type, library)` is a factory
that dispatches to specialized loaders via a `module_loaders` dict:
@@ -173,7 +167,7 @@ that dispatches to specialized loaders via a `module_loaders` dict:
`TransformerLoader` reads `config.json` from the component directory,
resolves the class via `ModelRegistry.resolve_model_cls()`, instantiates
the model, and loads safetensors weights. CPU offload and layerwise
offload are applied based on `resolved_config.engine.offload`.
offload are applied based on `FastVideoArgs`.
Unknown module types fall back to `GenericComponentLoader`.
@@ -211,14 +205,14 @@ loading by calling `ComponentLoader.for_module_type()` then `.load()`.
Abstract base class using the Template Method pattern:
- `__call__(batch, resolved_config)` — orchestrates verification, timing,
- `__call__(batch, fastvideo_args)` — orchestrates verification, timing,
and error handling. Not overridden by subclasses.
- `forward(batch, resolved_config) -> ForwardBatch` — abstract, contains
- `forward(batch, fastvideo_args) -> ForwardBatch` — abstract, contains
the stage logic.
- `verify_input()` / `verify_output()` — optional hooks returning
`VerificationResult`. Default: no checks.
When `resolved_config.engine.enable_stage_verification` is `True`, `__call__`
When `fastvideo_args.enable_stage_verification` is `True`, `__call__`
runs input verification before `forward()` and output verification after.
When `envs.FASTVIDEO_STAGE_LOGGING` is set, execution time is measured
with `torch.cuda.synchronize()` and logged.
@@ -301,7 +295,7 @@ provides detailed error messages. Failed verification raises
Abstract base for all inference pipelines. Lifecycle:
1. **`__init__(model_path, resolved_config)`** — initializes distributed
1. **`__init__(model_path, fastvideo_args)`** — initializes distributed
environment via `maybe_init_distributed_environment_and_model_parallel
(tp_size, sp_size)`, then calls `load_modules()` to populate
`self.modules`.
@@ -309,7 +303,7 @@ Abstract base for all inference pipelines. Lifecycle:
setup), `create_pipeline_stages()` (abstract — subclasses wire stages),
optionally applies `torch.compile` to transformers, and calls
`warmup_sequence_parallel_communication()`.
3. **`forward(batch, resolved_config)`** — iterates `self.stages` calling
3. **`forward(batch, fastvideo_args)`** — iterates `self.stages` calling
each stage in order. Decorated with `@torch.no_grad()`.
Key class attributes:
@@ -322,9 +316,8 @@ Key methods:
- `add_stage(name, stage)` — appends to `_stages` list and
`_stage_name_mapping` dict, also sets attribute on `self`.
- `get_module(name, default)` — retrieves a loaded module.
- `from_pretrained(model_path, *, resolved_config)` — class method that
builds the pipeline from a resolved config (from
`resolve_inference_config({...})`) by calling `cls(...)` then `post_init()`.
- `from_pretrained(model_path, **kwargs)` — class method constructing
`FastVideoArgs` and calling `cls(...)` then `post_init()`.
### LoRAPipeline (`fastvideo/pipelines/lora_pipeline.py`)
@@ -359,12 +352,12 @@ Key APIs: `get_tp_rank()`, `get_tp_world_size()`, `get_sp_rank()`,
`warmup_sequence_parallel_communication()` pre-warms NCCL communicators
to avoid slow first forward passes.
Usage: `fastvideo generate --config run.yaml
--generator.engine.parallelism.tp_size N --generator.engine.parallelism.sp_size M`.
Usage: `torchrun --nproc-per-node=N -m fastvideo.entrypoints.cli.main
generate --model-path ... --tp-size N --sp-size M`.
### torch.compile Integration
When `resolved_config.engine.compile.enabled` is `True`,
When `fastvideo_args.enable_torch_compile` is `True`,
`_maybe_compile_pipeline_module()` checks for a `_compile_conditions`
attribute on the module. If present, only matching submodules are
compiled. Otherwise, the entire module is compiled. FSDP-wrapped
@@ -376,53 +369,47 @@ modules are skipped.
```python
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-14B-Diffusers",
{"engine": {"num_gpus": 1, "parallelism": {"tp_size": 1, "sp_size": 1}}},
model_path="Wan-AI/Wan2.1-T2V-14B-Diffusers",
num_gpus=1, tp_size=1, sp_size=1,
)
result = generator.generate_video(
prompt="A cat dancing",
height=720, width=1280, num_frames=81,
)
result = generator.generate({
"prompt": "A cat dancing",
"sampling": {"height": 720, "width": 1280, "num_frames": 81},
})
```
**CLI** (`fastvideo/entrypoints/cli/`):
```bash
# run.yaml holds `generator: {model_path: Wan-AI/Wan2.1-T2V-14B-Diffusers}`.
fastvideo generate \
--config run.yaml \
--request.prompt "A cat dancing" \
--generator.engine.num_gpus 1
--model-path "Wan-AI/Wan2.1-T2V-14B-Diffusers" \
--prompt "A cat dancing" \
--num-gpus 1
```
**ResolvedGeneratorConfig** (`fastvideo/api/resolution.py`): The frozen
runtime config that the executor, workers, pipelines, stages, and loaders
read. Key paths: `model_path`, `mode` (`ExecutionMode`),
`pipeline.workload_type` (`WorkloadType`), `engine.num_gpus`,
`engine.parallelism.tp_size`, `engine.parallelism.sp_size`,
`pipeline.components.lora_path`, `engine.offload.dit`,
`engine.offload.dit_layerwise`, `engine.compile.enabled`,
`engine.enable_stage_verification`, and `pipeline_config` (the frozen
`PipelineConfig`).
**FastVideoArgs** (`fastvideo/fastvideo_args.py`): Central args dataclass.
Key fields: `model_path`, `mode` (`ExecutionMode`), `workload_type`
(`WorkloadType`), `pipeline_config` (`PipelineConfig`), `num_gpus`,
`tp_size`, `sp_size`, `lora_path`, `dit_cpu_offload`,
`dit_layerwise_offload`, `enable_torch_compile`,
`enable_stage_verification`.
Built by `resolve_inference_config(config)`
(`fastvideo/api/inference_resolution.py`), which runs the named resolution
steps (environment variables, model defaults, derived values, validation) in
order, records each decision, and then builds the `PipelineConfig` from the
registry, applies a JSON config if provided, and freezes it.
Constructed via `FastVideoArgs.from_kwargs(**kwargs)` which resolves the
`PipelineConfig` from the registry, applies JSON config if provided, and
merges CLI overrides.
## End-to-End Inference Flow
```
User: VideoGenerator.from_pretrained(model_path, config)
User: VideoGenerator.from_pretrained(model_path, **kwargs)
│
├─ resolve_inference_config() → PipelineConfig resolved via registry
├─ FastVideoArgs.from_kwargs() → PipelineConfig resolved via registry
├─ get_model_info() → ModelInfo(pipeline_cls, sampling_param_cls, ...)
│ ├─ model_index.json read → _class_name extracted
│ ├─ pipeline_registry resolves pipeline_cls from _class_name
│ └─ config_registry resolves config classes from model_path
│
├─ pipeline_cls.__init__(model_path, resolved_config)
├─ pipeline_cls.__init__(model_path, fastvideo_args)
│ ├─ maybe_init_distributed(tp_size, sp_size)
│ └─ load_modules() → reads model_index.json, loads each component
│ ├─ ComponentLoader.for_module_type() → specialized loader
@@ -434,10 +421,10 @@ User: VideoGenerator.from_pretrained(model_path, config)
├─ torch.compile (if enabled)
└─ warmup_sequence_parallel_communication()
User: generator.generate(request)
User: generator.generate_video(prompt, ...)
│
├─ ForwardBatch constructed from SamplingParam + user args
└─ pipeline.forward(batch, resolved_config)
└─ pipeline.forward(batch, fastvideo_args)
├─ InputValidationStage → validates dims
├─ TextEncodingStage → prompt → embeddings
├─ ConditioningStage → prepares conditioning
+42 -57
View File
@@ -8,7 +8,7 @@ FastVideo automatically distributes the generation process when multiple GPUs ar
# Will use 4 GPUs in parallel for faster generation
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{"engine": {"num_gpus": 4}},
num_gpus=4,
)
```
@@ -30,53 +30,58 @@ bring-up: [Pair two NVIDIA DGX Sparks](../getting_started/installation/spark_pai
## Customizing Generation
`VideoGenerator.from_pretrained(model_path, config)` takes the startup
settings as a nested mapping at their typed config paths, such as
`{"engine": {"num_gpus": 2, "offload": {"dit": False}}}`; it is
`VideoGenerator.from_config` with `model_path` added to the mapping. Pass
generation settings to `VideoGenerator.generate` as a request:
- `PipelineConfig`: Initialization time parameters
- `SamplingParam`: Generation time parameters
You can customize generation behavior using `PipelineConfig` and
`SamplingParam`:
```python
from fastvideo import VideoGenerator
from fastvideo import VideoGenerator, SamplingParam, PipelineConfig
def main():
model_name = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
config = PipelineConfig.from_pretrained(model_name)
config.vae_precision = "fp16"
# Create the generator
generator = VideoGenerator.from_config({
"model_path": model_name,
"engine": {
"num_gpus": 1,
"offload": {"dit_layerwise": True},
"precision": {"vae": "fp16"},
},
})
generator = VideoGenerator.from_pretrained(
model_name,
num_gpus=1,
dit_layerwise_offload=True, # FastVideoArgs option
pipeline_config=config
)
# Create and customize sampling parameters
sampling_param = SamplingParam.from_pretrained("Wan-AI/Wan2.1-T2V-1.3B-Diffusers")
# How many frames to generate
sampling_param.num_frames = 45
# Video resolution (width, height)
sampling_param.width = 1024
sampling_param.height = 576
# How many steps we denoise the video (higher = better quality, slower generation)
sampling_param.num_inference_steps = 30
# How strongly the video conforms to the prompt (higher = more faithful to prompt)
sampling_param.guidance_scale = 7.5
# Random seed for reproducibility
sampling_param.seed = 42 # Optional, leave unset for random results
# Generate video with custom parameters
prompt = "A beautiful sunset over a calm ocean, with gentle waves."
video = generator.generate({
"prompt": prompt,
"sampling": {
# How many frames to generate
"num_frames": 45,
# Video resolution (width, height)
"width": 1024,
"height": 576,
# How many steps we denoise the video (higher = better quality, slower generation)
"num_inference_steps": 30,
# How strongly the video conforms to the prompt (higher = more faithful to prompt)
"guidance_scale": 7.5,
# Random seed for reproducibility
"seed": 42, # Optional, leave unset for random results
},
"output": {
"output_path": "my_videos/", # Controls where videos are saved
"save_video": True,
},
})
video = generator.generate_video(
prompt,
sampling_param=sampling_param,
output_path="my_videos/", # Controls where videos are saved
save_video=True
)
# If return_frames=True, frames are available in video.frames
print(f"Generated {len(video.frames)} frames")
# If return_frames=True, frames are available in video["frames"]
print(f"Generated {len(video['frames'])} frames")
if __name__ == '__main__':
main()
@@ -120,26 +125,6 @@ Override individual values from the CLI with dotted paths:
fastvideo generate --config config.yaml --request.sampling.seed 42
```
## Where a Value Came From
FastVideo resolves the generator config once at startup and records the source of every value: the input config
(`input`; `explicit` tells whether you wrote the value or it is the schema default), a `FASTVIDEO_*` environment
variable, the model's defaults, or a derived value. A worker's device policy and values read from checkpoint files
are recorded too.
```python
generator = VideoGenerator.from_config(config)
generator.resolved_config.provenance("engine.parallelism.sp_size")
# PathProvenance(path='engine.parallelism.sp_size', value=2, source='derive_parallel_sizes', ...)
result = generator.generate(request)
result.resolved_request.provenance("sampling.num_frames")
# PathProvenance(..., value=81, source='fill_sampling_defaults[preset wan_t2v_1_3b]', explicit=False)
```
`resolved_config.provenance_table()` lists every path. Every value is decided before resolution ends, including the
device offload policy and the checkpoint defaults; after that, `resolved_config` is read-only.
## Performance Optimization
For configuring optimizations, please see our [optimizations guide](optimizations.md)
+1 -1
View File
@@ -66,7 +66,7 @@ as above.
For exports without this sidecar, an explicit ladder is supported via
`MiniMaxH3PipelineConfig.dmd_denoising_steps`, or through the typed API's
`PipelineSelection(dmd_denoising_steps=[...])`. The shifts
`PipelineSelection(experimental={"dmd_denoising_steps": [...]})`. The shifts
still come from the checkpoint scheduler configs. Keep generic `flow_shift`
unset: H3 has separate video and audio shifts, not one shared shift.
+21 -24
View File
@@ -38,20 +38,18 @@ def main():
# Create a video generator with a pre-trained model
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{"engine": {"num_gpus": 1}}, # Adjust based on your hardware
num_gpus=1, # Adjust based on your hardware
)
# Define a prompt for your video
prompt = "A curious raccoon peers through a vibrant field of yellow sunflowers, its eyes wide with interest."
# Generate the video
video = generator.generate({
"prompt": prompt,
"output": {
"output_path": "my_videos/", # Controls where videos are saved
"save_video": True,
},
})
video = generator.generate_video(
prompt,
output_path="my_videos/", # Controls where videos are saved
save_video=True
)
if __name__ == '__main__':
main()
@@ -77,24 +75,23 @@ Please see the [support matrix](support_matrix.md) for the list of supported mod
You can generate a video starting from an initial image:
```python
from fastvideo import VideoGenerator
from fastvideo import VideoGenerator, SamplingParam
def main():
# Create the generator
model_name = "Wan-AI/Wan2.1-I2V-14B-480P-Diffusers"
generator = VideoGenerator.from_pretrained(model_name, {"engine": {"num_gpus": 1}})
generator = VideoGenerator.from_pretrained(model_name, num_gpus=1)
# Set up parameters with an initial image
sampling_param = SamplingParam.from_pretrained(model_name)
sampling_param.image_path = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"
sampling_param.num_frames = 107
# Generate video based on the image
prompt = "A photograph coming to life with gentle movement"
generator.generate({
"prompt": prompt,
# Set up parameters with an initial image
"inputs": {
"image_path": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg",
},
"sampling": {"num_frames": 107},
"output": {"output_path": "my_videos/", "save_video": True},
})
generator.generate_video(prompt, sampling_param=sampling_param,
output_path="my_videos/",
save_video=True)
if __name__ == '__main__':
main()
@@ -109,12 +106,12 @@ Common issues and their solutions:
If you encounter CUDA out of memory errors:
- Reduce `num_frames` or video resolution
- Enable FastVideo offloading options such as `engine.offload.dit_layerwise: true`
(single GPU) or `engine.use_fsdp_inference: true` (multi-GPU)
- Enable FastVideo offloading options such as `dit_layerwise_offload=True`
(single GPU) or `use_fsdp_inference=True` (multi-GPU)
- Try a smaller model or use distilled versions
- Use `engine.num_gpus` > 1 if multiple GPUs are available
- Try enabling FSDP inference with `engine.use_fsdp_inference: true` (may slow down generation)
- Try enabling DiT layerwise offload with `engine.offload.dit_layerwise: true` (now only a few models support this, but may introduce less overhead than FSDP)
- Use `num_gpus` > 1 if multiple GPUs are available
- Try enabling FSDP inference with `use_fsdp_inference=True` (may slow down generation)
- Try enabling DiT layerwise offload with `dit_layerwise_offload=True` (now only a few models support this, but may introduce less overhead than FSDP)
### Slow Generation
+1 -1
View File
@@ -70,7 +70,7 @@ Run the numerical tests against a local TAEHV checkout containing the released
weights:
```bash
FASTVIDEO_TEST_TAEH3_REFERENCE_DIR=/path/to/taehv \
TAEH3_REFERENCE_DIR=/path/to/taehv \
python -m pytest fastvideo/tests/mlx/test_mlx_taeh3.py -q
```
+36 -53
View File
@@ -4,17 +4,15 @@ This page describes how to use offloading techniques for inference to reduce GPU
## Default Behavior
```yaml
engine:
use_fsdp_inference: false
offload:
dit: true # dit_cpu_offload
dit_layerwise: true # dit_layerwise_offload
text_encoder: true # text_encoder_cpu_offload
image_encoder: true # image_encoder_cpu_offload
vae: true # vae_cpu_offload
pin_cpu_memory: true
lazy_module_load: null # auto
```python
dit_cpu_offload: bool = True
use_fsdp_inference: bool = False
dit_layerwise_offload: bool = True
text_encoder_cpu_offload: bool = True
image_encoder_cpu_offload: bool = True
vae_cpu_offload: bool = True
pin_cpu_memory: bool = True
lazy_module_load: bool | None = None
```
On unified-memory accelerators such as NVIDIA GB10 and Apple silicon, FastVideo
@@ -37,8 +35,8 @@ channels, DiT patch size) so those stages do not materialize weights just to
read two integers. The MLX FastH3 runtime always uses this phase order. When
host offload is off, DiT safetensors are read onto the accelerator instead of
CPU-then-copy. Both flags default to auto (`None`) and turn on for
unified-memory devices such as GB10; lazy then disables sequential. Set
`engine.offload.lazy_module_load: false` to keep every component resident (sequential may still
unified-memory devices such as GB10; lazy then disables sequential. Pass
`--no-lazy-module-load` to keep every component resident (sequential may still
auto-arm). Two-node Spark
jobs still need this split: sequence parallel replicates the DiT on each GB10
(~66 GiB of weights plus activations). See
@@ -47,10 +45,7 @@ jobs still need this split: sequence parallel replicates the DiT on each GB10
## Behavior Explanation
!!! note
`VideoGenerator.from_pretrained` accepts the option names below as keywords, except `lazy_module_load` and
`h3_sequential_load`. In a YAML config or a dotted override, each option is a typed field:
`engine.use_fsdp_inference`, `engine.offload.<field>` as listed in the defaults above, and
`pipeline.model.minimax_h3.sequential_load` for `h3_sequential_load`.
For CLI usage, replace underscores (`_`) with hyphens (`-`).
### `use_fsdp_inference`
@@ -109,8 +104,8 @@ because the encoder has been released.
Leave the default on Spark / DGX Spark when `lazy_module_load` is off. When
both would arm (the GB10 auto case), lazy owns deferral and sequential stands
down so VAE `torch.compile` can attach to the lazy proxy. Force
`pipeline.model.minimax_h3.sequential_load: true` only when you need the split on a discrete GPU without
lazy load. Set `pipeline.model.minimax_h3.sequential_load: false` when you need more than one prompt per
`--h3-sequential-load` only when you need the split on a discrete GPU without
lazy load. Use `--no-h3-sequential-load` when you need more than one prompt per
worker and have enough memory to keep the encoder.
### `text_encoder_cpu_offload`
@@ -165,7 +160,7 @@ options above cannot help with because they act after loading. It is
particularly relevant on unified-memory devices, where host and device draw on
the same pool and moving weights to the host frees nothing. FastVideo
auto-enables it there (`lazy_module_load=None`). Leave it off when the model
already fits, or set `engine.offload.lazy_module_load: false` to keep components resident for
already fits, or pass `--no-lazy-module-load` to keep components resident for
later `generate()` calls.
This option applies to inference only. Training keeps every component resident
@@ -208,25 +203,19 @@ from fastvideo import VideoGenerator
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{
"engine": {
"num_gpus": 1,
"offload": {
# Recommended for single GPU
"dit_layerwise": True,
# Enable if OOM happens
"vae": True,
"image_encoder": True,
"text_encoder": True,
# Speeds up CPU-GPU transfer
"pin_cpu_memory": True,
},
},
},
num_gpus=1,
# Recommended for single GPU
dit_layerwise_offload=True,
# Enable if OOM happens
vae_cpu_offload=True,
image_encoder_cpu_offload=True,
text_encoder_cpu_offload=True,
# Speeds up CPU-GPU transfer
pin_cpu_memory=True,
)
prompt = "A curious raccoon peers through a vibrant field of yellow sunflowers."
video = generator.generate({"prompt": prompt, "output": {"output_path": "output/", "save_video": True}})
video = generator.generate_video(prompt, output_path="output/", save_video=True)
```
### Multi-GPU with FSDP
@@ -236,24 +225,18 @@ from fastvideo import VideoGenerator
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{
"engine": {
"num_gpus": 2,
# Recommended for multi-GPU
"use_fsdp_inference": True,
"offload": {
"dit_layerwise": False,
"dit": False,
# Enable if OOM happens
"vae": True,
"image_encoder": True,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
num_gpus=2,
# Recommended for multi-GPU
use_fsdp_inference=True,
dit_layerwise_offload=False,
dit_cpu_offload=False,
# Enable if OOM happens
vae_cpu_offload=True,
image_encoder_cpu_offload=True,
text_encoder_cpu_offload=True,
pin_cpu_memory=True,
)
prompt = "A majestic lion strides across the golden savanna."
video = generator.generate({"prompt": prompt, "output": {"output_path": "output/", "save_video": True}})
video = generator.generate_video(prompt, output_path="output/", save_video=True)
```
+40 -64
View File
@@ -166,26 +166,22 @@ Enable FP4 attention via the `--nvfp4_fa4` flag:
python examples/inference/optimizations/fp4_attn_wan2_1_1_3b.py --nvfp4_fa4
```
Or in Python via the `engine.attention.nvfp4_fa4` field (resolution sets the env vars):
Or in Python via the `nvfp4_fa4` kwarg (sets env vars automatically):
```python
from fastvideo import VideoGenerator
gen = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{
"engine": {
"attention": {"nvfp4_fa4": True},
"num_gpus": 1,
"use_fsdp_inference": False, # FSDP is incompatible with FP4 pointer path
},
},
nvfp4_fa4=True,
num_gpus=1,
use_fsdp_inference=False, # FSDP is incompatible with FP4 pointer path
)
gen.generate(request={"prompt": "A raccoon in sunflowers", "output": {"save_video": True}})
gen.generate_video(prompt="A raccoon in sunflowers", save_video=True)
```
#### Known Limitations
- `engine.use_fsdp_inference: true` is incompatible with the FP4 path (FSDP shards invalidate tensor pointers)
- `use_fsdp_inference=True` is incompatible with the FP4 path (FSDP shards invalidate tensor pointers)
- Per-call cosine similarity vs BF16: ~0.99 (slight quantization error accumulates over denoising steps)
- Only supports `headdim >= 128`
@@ -209,15 +205,15 @@ import os
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = "ATTN_QAT_INFER"
from fastvideo import VideoGenerator
gen = VideoGenerator.from_config({
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # FSDP shards invalidate the FP4 tensor pointers
# Wan-2.1 uses the nvfp4_qat config (NVFP4 is LTX2-specific).
"quantization": {"transformer_quant": "nvfp4_qat"},
},
})
from fastvideo.layers.quantization import get_quantization_config
gen = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
num_gpus=1,
# Wan-2.1 uses the nvfp4_qat config (NVFP4 is LTX2-specific). Pass an
# instance — the bare string is not resolved on the from_pretrained path.
transformer_quant=get_quantization_config("nvfp4_qat")(),
use_fsdp_inference=False, # FSDP shards invalidate the FP4 tensor pointers
)
gen.generate(request={"prompt": "A raccoon in sunflowers", "output": {"save_video": True}})
```
@@ -305,34 +301,17 @@ automatically.
### Usage
```python
from fastvideo import VideoGenerator
gen = VideoGenerator.from_config({
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"engine": {"quantization": {"transformer_quant": "FP8"}}, # per-tensor (default)
})
gen.generate(request={"prompt": "A raccoon in sunflowers", "output": {"save_video": True}})
```
`engine.quantization.transformer_quant` takes a quantization registry name and builds that config with its default
arguments. To pass constructor arguments, such as per-channel granularity, set the config instance on the DiT config
through `pipeline.model.generic.dit` instead:
```python
from fastvideo import VideoGenerator
from fastvideo.layers.quantization import get_quantization_config
gen = VideoGenerator.from_config({
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"pipeline": {
"model": {
"generic": {
"dit": {"quant_config": get_quantization_config("FP8")(granularity="channel")}, # slower, higher accuracy
},
},
},
})
gen = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
# Pass an instance — the bare string is not resolved on the from_pretrained path.
transformer_quant=get_quantization_config("FP8")(), # per-tensor (default)
# transformer_quant=get_quantization_config("FP8")(granularity="channel"), # slower, higher accuracy
)
gen.generate(request={"prompt": "A raccoon in sunflowers", "output": {"save_video": True}})
```
Or run the example script:
@@ -362,7 +341,7 @@ end-to-end speedup. It is **off by default** and enabled per-run.
```python
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{"engine": {"compile": {"enabled": True}}},
enable_torch_compile=True,
)
```
@@ -397,14 +376,12 @@ device is unsupported. Legacy VSA, MiniMax-H3 tile-256 VSA, and the explicit
eager with one warning instead of failing mid-denoise.
```python
generator = VideoGenerator.from_config({
"model_path": "MiniMaxAI/MiniMax-H3",
"engine": {"compile": {"regional": True}}, # or FASTVIDEO_INFERENCE_TORCH_COMPILE=1
})
generator = VideoGenerator.from_pretrained(
"MiniMaxAI/MiniMax-H3",
inference_torch_compile=True, # or FASTVIDEO_INFERENCE_TORCH_COMPILE=1
)
```
In a YAML config, set `generator.engine.compile.regional: true`.
Do not combine it with `torch_compile_kwargs['mode']` (the loader injects
inductor options, and torch.compile forbids mode+options); it is
independent of `enable_torch_compile`, and when both are set the regional
@@ -413,7 +390,7 @@ compile wins for the DiT.
### What to expect from generic compile
The Wan result below measures the existing generic
`engine.compile.enabled: true` path. It is useful evidence that compile can help,
`enable_torch_compile=True` path. It is useful evidence that compile can help,
but it is **not** a benchmark or numerical gate for the stricter regional
fullgraph path above.
@@ -456,12 +433,12 @@ not asserted by any standing SSIM regression here — the SSIM tests in
run with `enable_torch_compile` disabled. If you depend on compile
output staying close to eager (or your previous compiled run), run an
MS-SSIM gate on *your* config, especially when combining
`engine.compile.enabled: true` with other numerics-affecting flags
`enable_torch_compile=True` with other numerics-affecting flags
(quantized attention backends, FP4, layerwise offload edge cases).
### Known interactions
- **Layerwise CPU offload** (`engine.offload.dit_layerwise: true`, the default):
- **Layerwise CPU offload** (`dit_layerwise_offload=True`, the default):
the offload hook previously caused an implicit graph break once per
transformer layer, fragmenting the compiled region. Addressed in
hao-ai-lab/FastVideo#1365 — keep that fix to get a clean compiled
@@ -476,17 +453,16 @@ MS-SSIM gate on *your* config, especially when combining
grad-enabled path remain outside it. Use the default inductor mode shown
above unless your exact configuration has its own gate.
Extra `torch.compile` options live at `engine.compile.backend`,
`fullgraph`, `mode`, and `dynamic`; any other `torch.compile` kwargs go in
`engine.compile.extras`. Set them in the nested config, in a config file,
or as a CLI dotted override (for example
`--generator.engine.compile.mode reduce-overhead`). Example (currently
Extra `torch.compile` options are passed through `torch_compile_kwargs`
(a dict), accepted by `VideoGenerator.from_pretrained(...)` and by the
CLI as a JSON string via `--torch-compile-kwargs`. Example (currently
**not** recommended — see the CUDA-graphs caveat above):
```python
VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{"engine": {"compile": {"enabled": True, "mode": "reduce-overhead"}}}, # may error today
enable_torch_compile=True,
torch_compile_kwargs={"mode": "reduce-overhead"}, # may error today
)
```
@@ -499,7 +475,7 @@ config; **discard the first generation** (graph build):
import time
from fastvideo import VideoGenerator
gen = VideoGenerator.from_pretrained("your-model-id", {"engine": {"compile": {"enabled": True}}})
gen = VideoGenerator.from_pretrained("your-model-id", enable_torch_compile=True)
req = {"prompt": "Your prompt", "sampling": {"seed": 1024},
"output": {"save_video": False}}
gen.generate(req) # warmup: graph build, discard
@@ -523,10 +499,10 @@ for backend in ["TORCH_SDPA", "FLASH_ATTN", "SAGE_ATTN"]:
os.environ["FASTVIDEO_ATTENTION_BACKEND"] = backend
generator = VideoGenerator.from_pretrained("your-model-id")
start_time = time.perf_counter()
generator.generate({
"prompt": "Your prompt",
"sampling": {"seed": 1024},
})
generator.generate_video(
prompt="Your prompt",
seed=1024,
)
elapsed = time.perf_counter() - start_time
print(f"{backend}: {elapsed:.2f}s")
```
+28 -38
View File
@@ -19,56 +19,46 @@ bash examples/training/finetune/wan_t2v_1.3B/crush_smol/preprocess_wan_data_t2v_
## Preprocessing Pipeline
The preprocessing pipeline supports multiple dataset formats and video loaders. It reads a `PreprocessRunConfig`
YAML file (`fastvideo/api/training_schema.py`): the model and workload type at the top level, and the preprocessing
settings in the `preprocess` section (the fields of `PreprocessConfig` in `fastvideo/configs/configs.py`):
```yaml
# preprocess_t2v.yaml
model_path: Wan-AI/Wan2.1-T2V-1.3B-Diffusers
pipeline:
workload_type: t2v
preprocess:
video_loader_type: torchvision
dataset_type: merged
preprocess_video_batch_size: 2
dataloader_num_workers: 0
max_height: 480
max_width: 832
num_frames: 77
train_fps: 16
samples_per_file: 8
flush_frequency: 8
video_length_tolerance_range: 5
```
Pass the file with `--config`. Each dotted override after it sets one field, for example the values that come from
shell variables:
The new preprocessing pipeline supports multiple dataset formats and video loaders:
```bash
GPU_NUM=2
MODEL_PATH="Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
DATASET_PATH="data/crush-smol/"
OUTPUT_DIR="data/crush-smol_processed_t2v/"
torchrun --nproc_per_node=$GPU_NUM \
-m fastvideo.pipelines.preprocess.v1_preprocessing_new \
--config preprocess_t2v.yaml \
--preprocess.dataset_path "$DATASET_PATH" \
--preprocess.dataset_output_dir "$OUTPUT_DIR"
--model_path $MODEL_PATH \
--mode preprocess \
--workload_type t2v \
--preprocess.video_loader_type torchvision \
--preprocess.dataset_type merged \
--preprocess.dataset_path $DATASET_PATH \
--preprocess.dataset_output_dir $OUTPUT_DIR \
--preprocess.preprocess_video_batch_size 2 \
--preprocess.dataloader_num_workers 0 \
--preprocess.max_height 480 \
--preprocess.max_width 832 \
--preprocess.num_frames 77 \
--preprocess.train_fps 16 \
--preprocess.samples_per_file 8 \
--preprocess.flush_frequency 8 \
--preprocess.video_length_tolerance_range 5
```
### Key Parameters
| Parameter | Description |
| ------------------------------------- | ----------------------------------------------------------- |
| `pipeline.workload_type` | Task type: `t2v` (text-to-video) or `i2v` (image-to-video) |
| `preprocess.dataset_type` | Input format: `hf` (HuggingFace) or `merged` (local folder) |
| `preprocess.dataset_path` | Path to dataset (HF repo ID or local folder) |
| `preprocess.dataset_output_dir` | Output directory for Parquet files |
| `preprocess.video_loader_type` | Video decoder: `torchcodec` or `torchvision` |
| `preprocess.max_height` / `max_width` | Target resolution for videos |
| `preprocess.num_frames` | Number of frames to extract per video |
| `preprocess.train_fps` | Target FPS for frame extraction |
| Parameter | Description |
|-----------|-------------|
| `--workload_type` | Task type: `t2v` (text-to-video) or `i2v` (image-to-video) |
| `--preprocess.dataset_type` | Input format: `hf` (HuggingFace) or `merged` (local folder) |
| `--preprocess.dataset_path` | Path to dataset (HF repo ID or local folder) |
| `--preprocess.dataset_output_dir` | Output directory for Parquet files |
| `--preprocess.video_loader_type` | Video decoder: `torchcodec` or `torchvision` |
| `--preprocess.max_height` / `max_width` | Target resolution for videos |
| `--preprocess.num_frames` | Number of frames to extract per video |
| `--preprocess.train_fps` | Target FPS for frame extraction |
## Dataset Formats
+37 -48
View File
@@ -4,58 +4,48 @@ This guide covers finetuning video diffusion models with FastVideo, including fu
## Training Arguments
Each training launcher passes a `TrainingRunConfig` YAML file to its entry point with `--config` (the schema is in
`fastvideo/api/training_schema.py`). A dotted override after `--config` sets one field, for example
`--training.optimizer.learning_rate 1e-5` or `--engine.num_gpus "$NUM_GPUS"`:
```bash
torchrun --nnodes 1 --nproc_per_node 4 \
fastvideo/training/wan_training_pipeline.py \
--config finetune_t2v.yaml \
--engine.num_gpus 4
```
The settings are grouped as follows:
FastVideo training scripts use several argument groups:
### Training Arguments
| Config path | Description |
| ------------------------------------------------------ | ------------------------------------------------- |
| `training.loop.max_train_steps` | Total training steps |
| `training.data.train_batch_size` | Batch size per GPU |
| `training.loop.gradient_accumulation_steps` | Steps to accumulate before optimizer update |
| `training.data.num_latent_t` | Temporal latent dimension (reduce to save memory) |
| `training.data.num_height` / `training.data.num_width` | Video resolution |
| `training.data.num_frames` | Number of frames per video |
| `training.checkpoint.output_dir` | Directory for checkpoints |
| Argument | Description |
|----------|-------------|
| `--max_train_steps` | Total training steps |
| `--train_batch_size` | Batch size per GPU |
| `--gradient_accumulation_steps` | Steps to accumulate before optimizer update |
| `--num_latent_t` | Temporal latent dimension (reduce to save memory) |
| `--num_height` / `--num_width` | Video resolution |
| `--num_frames` | Number of frames per video |
| `--output_dir` | Directory for checkpoints |
### Parallelism Arguments
| Config path | Description |
| --------------------------------------- | ---------------------------------------------------------- |
| `engine.num_gpus` | Total number of GPUs |
| `engine.parallelism.sp_size` | Sequence parallel size (increase to reduce memory per GPU) |
| `engine.parallelism.tp_size` | Tensor parallel size |
| `engine.parallelism.hsdp_replicate_dim` | HSDP replication dimension |
| `engine.parallelism.hsdp_shard_dim` | HSDP sharding dimension |
| Argument | Description |
|----------|-------------|
| `--num_gpus` | Total number of GPUs |
| `--sp_size` | Sequence parallel size (increase to reduce memory per GPU) |
| `--tp_size` | Tensor parallel size |
| `--hsdp_replicate_dim` | HSDP replication dimension |
| `--hsdp_shard_dim` | HSDP sharding dimension |
### Optimizer Arguments
| Config path | Description |
| ---------------------------------- | ------------------------------- |
| `training.optimizer.learning_rate` | Base learning rate |
| `training.optimizer.weight_decay` | Weight decay for regularization |
| `training.optimizer.max_grad_norm` | Gradient clipping threshold |
| Argument | Description |
|----------|-------------|
| `--learning_rate` | Base learning rate |
| `--mixed_precision` | Precision mode (`bf16` recommended) |
| `--weight_decay` | Weight decay for regularization |
| `--max_grad_norm` | Gradient clipping threshold |
### Validation Arguments
| Config path | Description |
| ------------------------------------ | ----------------------------------------------------------- |
| `training.validation.enabled` | Enable validation logging |
| `training.validation.dataset_file` | JSON file with validation prompts |
| `training.validation.every_steps` | Run validation every N steps |
| `training.validation.sampling_steps` | Inference steps for validation (a list, for example `[50]`) |
| `training.validation.guidance_scale` | CFG scale for validation |
| Argument | Description |
|----------|-------------|
| `--log_validation` | Enable validation logging |
| `--validation_dataset_file` | JSON file with validation prompts |
| `--validation_steps` | Run validation every N steps |
| `--validation_sampling_steps` | Inference steps for validation |
| `--validation_guidance_scale` | CFG scale for validation |
## Full Finetuning
@@ -69,8 +59,8 @@ bash examples/training/finetune/wan_t2v_1.3B/crush_smol/finetune_t2v.sh
**Typical settings:**
- Learning rate: `1e-5` to `5e-5`
- Gradient checkpointing: `training.model.enable_gradient_checkpointing_type: full`
- Memory scaling: Increase `engine.parallelism.sp_size` or reduce `training.data.num_latent_t` to fit in memory
- Gradient checkpointing: `--enable_gradient_checkpointing_type "full"`
- Memory scaling: Increase `--sp_size` or reduce `--num_latent_t` to fit in memory
## Attention Quantization-Aware Training
@@ -90,10 +80,10 @@ LoRA (Low-Rank Adaptation) trains lightweight adapters while keeping the base mo
### LoRA-Specific Arguments
| Config path | Description |
| ----------------------------- | --------------------------------------- |
| `training.lora.enabled: true` | Enable LoRA mode |
| `training.lora.rank` | Rank of LoRA adapters (16, 32, 64, 128) |
| Argument | Description |
|----------|-------------|
| `--lora_training True` | Enable LoRA mode |
| `--lora_rank` | Rank of LoRA adapters (16, 32, 64, 128) |
### Learning Rate for LoRA
@@ -113,7 +103,7 @@ bash examples/training/finetune/wan_t2v_1.3B/crush_smol/finetune_t2v_lora.sh
Key differences from full finetune:
- Set `training.lora.enabled: true` and `training.lora.rank: 32`
- Add `--lora_training True --lora_rank 32`
- Use higher learning rate (10–20× full finetune)
- Can run on fewer GPUs (even single GPU)
- Outputs adapter weights instead of full model
@@ -196,5 +186,4 @@ Each example includes:
- `preprocess_*.sh` — run preprocessing
- `finetune_*.sh` — full finetune launcher
- `finetune_*_lora.sh` — LoRA finetune launcher
- a YAML file next to each launcher — the `TrainingRunConfig` that the launcher passes with `--config`
- `validation.json` — validation prompts
+104 -10
View File
@@ -16,17 +16,111 @@ DATA_DIR="data/matrixgame2"
VALIDATION_DATASET_FILE="examples/distill/MatrixGame2.0/validation.json"
NUM_GPUS=1
# Training arguments
training_args=(
--tracker_project_name "matrixgame2_sf"
--output_dir "checkpoints/matrixgame2_sf_${RUN_NAME}"
--wandb_run_name "${RUN_NAME}_test"
--max_train_steps 5
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 352
--num_width 640
--enable_gradient_checkpointing_type "full"
--simulate_generator_forward
--num_frames 81
--num_frame_per_block 3 # Frame generation block size for self-forcing
# --enable_gradient_masking
# --gradient_mask_last_n_frames 21
# --init_weights_from_safetensors "path/to/generator_ema.safetensors"
)
# Parallel arguments
parallel_args=(
--num_gpus $NUM_GPUS
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1
--hsdp_shard_dim $NUM_GPUS
)
model_args=(
--model_path $GENERATOR_MODEL_PATH # TODO: check if you can remove this in this script
--pretrained_model_name_or_path $GENERATOR_MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--log_visualization
--visualization-steps 100
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 100
--validation_sampling_steps "4"
--validation_guidance_scale "6.0"
)
# Optimizer arguments
optimizer_args=(
--learning_rate 3e-6
--mixed_precision "bf16"
--weight_only_checkpointing_steps 400
--training_state_checkpointing_steps 400
--weight_decay 0
--betas "0.9,0.95"
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--flow_shift 5
--seed 1000
--use_ema True
--ema_decay 0.99
--ema_start_step 200
)
dmd_args=(
--dmd_denoising_steps '1000,750,500,250'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--dfake_gen_update_ratio 5
--real_score_guidance_scale 3.0
--fake_score_learning_rate 3e-7
--fake_score_betas "0.9,0.95"
--warp_denoising_step
)
self_forcing_args=(
--independent_first_frame False # Whether to treat first frame independently
--same_step_across_blocks True # Whether to use same denoising step across all blocks
--last_step_only False # Whether to only use the last denoising step
--context_noise 0 # Amount of noise to add during context caching (0 = no noise)
)
torchrun \
--nnodes 1 \
--nproc_per_node $NUM_GPUS \
fastvideo/training/matrixgame2_self_forcing_distillation_pipeline.py \
--config examples/distill/MatrixGame2.0/distill_dmd.yaml \
--model_path "$GENERATOR_MODEL_PATH" \
--engine.num_gpus "$NUM_GPUS" \
--engine.parallelism.hsdp_shard_dim "$NUM_GPUS" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "checkpoints/matrixgame2_sf_${RUN_NAME}" \
--training.tracker.run_name "${RUN_NAME}_test" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}" \
"${self_forcing_args[@]}"
@@ -63,6 +63,93 @@ FAKE_SCORE_MODEL_PATH="FastVideo/Matrix-Game-2.0-Base-Diffusers"
DATA_DIR="data/matrixgame2"
VALIDATION_DATASET_FILE="examples/distill/MatrixGame2.0/validation.json"
training_args=(
--tracker_project_name "matrixgame2_sf"
--output_dir "checkpoints/matrixgame2_sf_${RUN_NAME}"
--wandb_run_name "${RUN_NAME}_test"
--max_train_steps 1200
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 2
--num_latent_t 21
--num_height 352
--num_width 640
--simulate_generator_forward
--num_frames 81
--num_frame_per_block 3
# --init_weights_from_safetensors "path/to/generator_ema.safetensors"
)
parallel_args=(
--num_gpus "${TOTAL_GPUS}"
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1
--hsdp_shard_dim "${TOTAL_GPUS}"
)
model_args=(
--model_path "${GENERATOR_MODEL_PATH}"
--pretrained_model_name_or_path "${GENERATOR_MODEL_PATH}"
--real_score_model_path "${REAL_SCORE_MODEL_PATH}"
--fake_score_model_path "${FAKE_SCORE_MODEL_PATH}"
)
dataset_args=(
--data_path "${DATA_DIR}"
--dataloader_num_workers 4
)
validation_args=(
--log_validation
--log_visualization
--visualization-steps 100
--validation_dataset_file "${VALIDATION_DATASET_FILE}"
--validation_steps 100
--validation_sampling_steps "4"
--validation_guidance_scale "6.0"
)
optimizer_args=(
--learning_rate 3e-6
--mixed_precision "bf16"
--weight_only_checkpointing_steps 400
--training_state_checkpointing_steps 400
--weight_decay 0
--betas "0.9,0.95"
--max_grad_norm 1.0
)
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--flow_shift 5
--seed 1000
--use_ema True
--ema_decay 0.99
--ema_start_step 200
)
dmd_args=(
--dmd_denoising_steps "1000,750,500,250"
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--dfake_gen_update_ratio 5
--real_score_guidance_scale 3.0
--fake_score_learning_rate 3e-7
--fake_score_betas "0.9,0.95"
--warp_denoising_step
)
self_forcing_args=(
--independent_first_frame False
--same_step_across_blocks True
--last_step_only False
--context_noise 0
)
srun python -m torch.distributed.run \
--nnodes "${SLURM_JOB_NUM_NODES}" \
--nproc_per_node "${GPUS_PER_NODE}" \
@@ -70,13 +157,12 @@ srun python -m torch.distributed.run \
--rdzv_backend=c10d \
--rdzv_endpoint="${MASTER_ADDR}:${MASTER_PORT}" \
fastvideo/training/matrixgame2_self_forcing_distillation_pipeline.py \
--config examples/distill/MatrixGame2.0/distill_dmd_slurm.yaml \
--model_path "${GENERATOR_MODEL_PATH}" \
--engine.num_gpus "${TOTAL_GPUS}" \
--engine.parallelism.hsdp_shard_dim "${TOTAL_GPUS}" \
--training.distillation.real_score_model_path "${REAL_SCORE_MODEL_PATH}" \
--training.distillation.fake_score_model_path "${FAKE_SCORE_MODEL_PATH}" \
--training.data.data_path "${DATA_DIR}" \
--training.checkpoint.output_dir "checkpoints/matrixgame2_sf_${RUN_NAME}" \
--training.tracker.run_name "${RUN_NAME}_test" \
--training.validation.dataset_file "${VALIDATION_DATASET_FILE}"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}" \
"${self_forcing_args[@]}"
@@ -1,68 +0,0 @@
# Read by distill_dmd.sh, which passes model_path, engine.num_gpus, engine.parallelism.hsdp_shard_dim,
# training.distillation.real_score_model_path, training.distillation.fake_score_model_path, training.data.data_path,
# training.checkpoint.output_dir, training.tracker.run_name, and training.validation.dataset_file on the command line.
engine:
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1
precision:
dit: fp32
pipeline:
# components:
# transformer_weights: path/to/generator_ema.safetensors
flow_shift: 5.0
dmd_denoising_steps: [1000, 750, 500, 250]
training:
data:
dataloader_num_workers: 4
num_height: 352
num_width: 640
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 3.0e-06
betas: [0.9, 0.95]
weight_decay: 0.0
max_grad_norm: 1.0
loop:
max_train_steps: 5
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 400
weight_only_checkpointing_steps: 400
checkpoints_total_limit: 3
tracker:
project_name: matrixgame2_sf
validation:
enabled: true
sampling_steps: [4]
guidance_scale: 6.0
every_steps: 100
log_visualization: true
visualization_steps: 100
distillation:
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 3.0e-07
fake_score_betas: [0.9, 0.95]
simulate_generator_forward: true
warp_denoising_step: true
ema:
enabled: true
decay: 0.99
start_step: 200
self_forcing:
dfake_gen_update_ratio: 5
num_frame_per_block: 3 # Frame generation block size for self-forcing
independent_first_frame: false # Whether to treat first frame independently
same_step_across_blocks: true # Whether to use same denoising step across all blocks
last_step_only: false # Whether to only use the last denoising step
context_noise: 0 # Amount of noise to add during context caching (0 = no noise)
model:
enable_gradient_checkpointing_type: full
@@ -1,66 +0,0 @@
# Read by distill_dmd.slurm, which passes model_path, engine.num_gpus, engine.parallelism.hsdp_shard_dim,
# training.distillation.real_score_model_path, training.distillation.fake_score_model_path, training.data.data_path,
# training.checkpoint.output_dir, training.tracker.run_name, and training.validation.dataset_file on the command line.
engine:
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1
precision:
dit: fp32
pipeline:
# components:
# transformer_weights: path/to/generator_ema.safetensors
flow_shift: 5.0
dmd_denoising_steps: [1000, 750, 500, 250]
training:
data:
dataloader_num_workers: 4
num_height: 352
num_width: 640
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 3.0e-06
betas: [0.9, 0.95]
weight_decay: 0.0
max_grad_norm: 1.0
loop:
max_train_steps: 1200
gradient_accumulation_steps: 2
checkpoint:
training_state_checkpointing_steps: 400
weight_only_checkpointing_steps: 400
checkpoints_total_limit: 3
tracker:
project_name: matrixgame2_sf
validation:
enabled: true
sampling_steps: [4]
guidance_scale: 6.0
every_steps: 100
log_visualization: true
visualization_steps: 100
distillation:
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 3.0e-07
fake_score_betas: [0.9, 0.95]
simulate_generator_forward: true
warp_denoising_step: true
ema:
enabled: true
decay: 0.99
start_step: 200
self_forcing:
dfake_gen_update_ratio: 5
num_frame_per_block: 3
independent_first_frame: false
same_step_across_blocks: true
last_step_only: false
context_noise: 0
@@ -36,16 +36,105 @@ VALIDATION_DATASET_FILE=your_validation_data_dir
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
training_args=(
--tracker_project_name SFwan_t2v_distill_self_forcing_dmd
--output_dir your_output_dir
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 480
--num_width 832
--enable_gradient_checkpointing_type "full"
--log_visualization
--simulate_generator_forward
--num_frames 81
--num_frame_per_block 3 # Frame generation block size for self-forcing
--enable_gradient_masking
--gradient_mask_last_n_frames 21
)
parallel_args=(
--num_gpus $NUM_GPUS # 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1 # 64
--hsdp_shard_dim $NUM_GPUS
)
model_args=(
--model_path $GENERATOR_MODEL_PATH # TODO: check if you can remove this in this script
--pretrained_model_name_or_path $GENERATOR_MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 50
--validation_sampling_steps "4"
--validation_guidance_scale "6.0" # not used for dmd inference
)
optimizer_args=(
--learning_rate 1e-5
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
--weight_decay 0.01
--betas '0.0,0.999'
--max_grad_norm 1.0
)
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--flow_shift 5
--seed 1000
--use_ema True
--ema_decay 0.99
--ema_start_step 100
--init_weights_from_safetensors your_ode_init_weights_path
)
dmd_args=(
--dmd_denoising_steps '1000,750,500,250'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--dfake_gen_update_ratio 5
--real_score_guidance_scale 3.0
--fake_score_learning_rate 8e-6
--fake_score_betas '0.0,0.999'
--warp_denoising_step
)
self_forcing_args=(
--independent_first_frame False # Whether to treat first frame independently
--same_step_across_blocks True # Whether to use same denoising step across all blocks
--last_step_only False # Whether to only use the last denoising step
--context_noise 0 # Amount of noise to add during context caching (0 = no noise)
)
torchrun \
--nnodes 1 \
--master_port $MASTER_PORT \
--nproc_per_node $NUM_GPUS \
fastvideo/training/wan_self_forcing_distillation_pipeline.py \
--config examples/distill/SFWan2.1-T2V/distill_dmd_t2v_1.3B.yaml \
--model_path "$GENERATOR_MODEL_PATH" \
--engine.num_gpus "$NUM_GPUS" \
--engine.parallelism.hsdp_shard_dim "$NUM_GPUS" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}" \
"${self_forcing_args[@]}"
@@ -1,68 +0,0 @@
# Read by distill_dmd_t2v_1.3B.sh, which passes model_path, engine.num_gpus, engine.parallelism.hsdp_shard_dim,
# training.distillation.real_score_model_path, training.distillation.fake_score_model_path, training.data.data_path, and
# training.validation.dataset_file on the command line.
engine:
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1 # 64
precision:
dit: fp32
pipeline:
components:
transformer_weights: your_ode_init_weights_path
flow_shift: 5.0
dmd_denoising_steps: [1000, 750, 500, 250]
training:
data:
dataloader_num_workers: 4
num_height: 480
num_width: 832
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 1.0e-05
betas: [0.0, 0.999]
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
output_dir: your_output_dir
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: SFwan_t2v_distill_self_forcing_dmd
validation:
enabled: true
sampling_steps: [4]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 50
log_visualization: true
distillation:
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 8.0e-06
fake_score_betas: [0.0, 0.999]
simulate_generator_forward: true
warp_denoising_step: true
ema:
enabled: true
decay: 0.99
start_step: 100
self_forcing:
dfake_gen_update_ratio: 5
num_frame_per_block: 3 # Frame generation block size for self-forcing
independent_first_frame: false # Whether to treat first frame independently
same_step_across_blocks: true # Whether to use same denoising step across all blocks
last_step_only: false # Whether to only use the last denoising step
context_noise: 0 # Amount of noise to add during context caching (0 = no noise)
model:
enable_gradient_checkpointing_type: full
@@ -8,7 +8,17 @@ OUTPUT_DIR="data/crush-smol_processed_t2v/"
torchrun --nproc_per_node=$GPU_NUM \
fastvideo/pipelines/preprocess/v1_preprocess.py \
--config examples/distill/SFWan2.1-T2V/preprocess_data.yaml \
--model_path $MODEL_PATH \
--preprocess.data_merge_path $DATA_MERGE_PATH \
--preprocess.dataset_output_dir=$OUTPUT_DIR
--data_merge_path $DATA_MERGE_PATH \
--preprocess_video_batch_size 8 \
--seed 42 \
--max_height 480 \
--max_width 832 \
--num_frames 81 \
--dataloader_num_workers 0 \
--output_dir=$OUTPUT_DIR \
--train_fps 16 \
--samples_per_file 8 \
--flush_frequency 8 \
--video_length_tolerance_range 5 \
--preprocess_task "t2v"
@@ -1,19 +0,0 @@
# Read by preprocess_data.sh, which passes model_path, preprocess.data_merge_path, and preprocess.dataset_output_dir.
# Offload settings of the per-task preprocessing pipelines (v1_preprocess.py).
engine:
offload:
dit_layerwise: true
image_encoder: true
pin_cpu_memory: true
preprocess:
preprocess_video_batch_size: 8
seed: 42
max_height: 480
max_width: 832
num_frames: 81
dataloader_num_workers: 0
train_fps: 16
samples_per_file: 8
flush_frequency: 8
video_length_tolerance_range: 5
preprocess_task: t2v
+99 -6
View File
@@ -49,6 +49,96 @@ VALIDATION_DATASET_FILE="/mnt/weka/home/hao.zhang/wl/FastVideo/examples/distill/
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
training_args=(
--tracker_project_name SFwan2.2_t2v_distill_self_forcing_dmd # Updated for Wan2.2
--output_dir "/mnt/sharefs/users/hao.zhang/SFwan2.2_t2v_finetune"
--override_transformer_cls_name "CausalWanTransformer3DModel"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 448 # Updated to match Wan2.2 config
--num_width 832 # Updated to match Wan2.2 config
--enable_gradient_checkpointing_type "full"
--simulate_generator_forward
# --log_visualization
--num_frames 81
--num_frame_per_block 3 # Frame generation block size for self-forcing
--enable_gradient_masking
--gradient_mask_last_n_frames 21
# --init_weights_from_safetensors /mnt/sharefs/users/hao.zhang/wl/models/sf_ode_init_wan22_checkpoints/high/3k/
# --init_weights_from_safetensors_2 /mnt/sharefs/users/hao.zhang/wl/models/sf_ode_init_wan22_checkpoints/low/3k/
)
parallel_args=(
--num_gpus 32 # 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1 # 64
--hsdp_shard_dim 32
)
model_args=(
--model_path $GENERATOR_MODEL_PATH
--pretrained_model_name_or_path $GENERATOR_MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 20
--validation_sampling_steps "4"
--validation_guidance_scale "6.0" # not used for dmd inference
)
optimizer_args=(
--learning_rate 1e-5
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
--weight_decay 0.01
--betas '0.0,0.999'
--max_grad_norm 1.0
)
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--flow_shift 5
--seed 1000
--use_ema True
--ema_decay 0.99
--ema_start_step 100
)
dmd_args=(
--dmd_denoising_steps '1000,750,500,250'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--dfake_gen_update_ratio 5
--real_score_guidance_scale 3.0
--fake_score_learning_rate 8e-6
--fake_score_betas '0.0,0.999'
--warp_denoising_step
)
self_forcing_args=(
--independent_first_frame False # Whether to treat first frame independently
--same_step_across_blocks True # Whether to use same denoising step across all blocks
--last_step_only False # Whether to only use the last denoising step
--context_noise 0 # Amount of noise to add during context caching (0 = no noise)
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -56,9 +146,12 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_self_forcing_distillation_pipeline.py \
--config examples/distill/SFWan2.2-A14B/distill_dmd.yaml \
--model_path "$GENERATOR_MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}" \
"${self_forcing_args[@]}"
@@ -1,72 +0,0 @@
# Read by distill_dmd.sh, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, and training.validation.dataset_file on the
# command line.
engine:
num_gpus: 32 # 64
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1 # 64
hsdp_shard_dim: 32
precision:
dit: fp32
pipeline:
components:
# transformer_weights: /mnt/sharefs/users/hao.zhang/wl/models/sf_ode_init_wan22_checkpoints/high/3k/
# transformer_2_weights: /mnt/sharefs/users/hao.zhang/wl/models/sf_ode_init_wan22_checkpoints/low/3k/
override_transformer_cls_name: CausalWanTransformer3DModel
flow_shift: 5.0
dmd_denoising_steps: [1000, 750, 500, 250]
training:
data:
dataloader_num_workers: 4
num_height: 448 # Updated to match Wan2.2 config
num_width: 832 # Updated to match Wan2.2 config
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 1.0e-05
betas: [0.0, 0.999]
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
output_dir: /mnt/sharefs/users/hao.zhang/SFwan2.2_t2v_finetune
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: SFwan2.2_t2v_distill_self_forcing_dmd # Updated for Wan2.2
validation:
enabled: true
sampling_steps: [4]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 20
# log_visualization: true
distillation:
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 8.0e-06
fake_score_betas: [0.0, 0.999]
simulate_generator_forward: true
warp_denoising_step: true
ema:
enabled: true
decay: 0.99
start_step: 100
self_forcing:
dfake_gen_update_ratio: 5
num_frame_per_block: 3 # Frame generation block size for self-forcing
independent_first_frame: false # Whether to treat first frame independently
same_step_across_blocks: true # Whether to use same denoising step across all blocks
last_step_only: false # Whether to only use the last denoising step
context_noise: 0 # Amount of noise to add during context caching (0 = no noise)
model:
enable_gradient_checkpointing_type: full
@@ -47,6 +47,84 @@ OUTPUT_DIR="checkpoints/wan_t2v_finetune"
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name wan_t2v_distill_dmd_VSA
--output_dir $OUTPUT_DIR
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 480
--num_width 832
--num_frames 81
--enable_gradient_checkpointing_type "full"
)
# Parallel arguments
parallel_args=(
--num_gpus 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 64
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 2e-6
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 8
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3.5
--VSA_sparsity 0.8
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -54,10 +132,11 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.1-T2V/Wan-Syn-Data-480P/distill_dmd_VSA_t2v_1.3B.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "$OUTPUT_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,55 +0,0 @@
# Read by distill_dmd_VSA_t2v_1.3B.slurm, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, training.checkpoint.output_dir, and
# training.validation.dataset_file on the command line.
engine:
num_gpus: 64
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 64
hsdp_shard_dim: 1
attention:
vsa_sparsity: 0.8
precision:
dit: fp32
pipeline:
flow_shift: 8.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 480
num_width: 832
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 2.0e-06
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: wan_t2v_distill_dmd_VSA
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.5
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -47,6 +47,84 @@ OUTPUT_DIR="checkpoints/wan_t2v_finetune"
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name wan_t2v_distill_dmd_VSA
--output_dir "$OUTPUT_DIR"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 480
--num_width 832
--num_frames 81
--enable_gradient_checkpointing_type "full"
)
# Parallel arguments
parallel_args=(
--num_gpus 64
--sp_size 4
--tp_size 1
--hsdp_replicate_dim 8
--hsdp_shard_dim 8
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 2e-6
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 3
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3.5
--VSA_sparsity 0.9
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -54,10 +132,11 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.1-T2V/Wan-Syn-Data-480P/distill_dmd_VSA_t2v_14B.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "$OUTPUT_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,55 +0,0 @@
# Read by distill_dmd_VSA_t2v_14B.slurm, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, training.checkpoint.output_dir, and
# training.validation.dataset_file on the command line.
engine:
num_gpus: 64
parallelism:
tp_size: 1
sp_size: 4
hsdp_replicate_dim: 8
hsdp_shard_dim: 8
attention:
vsa_sparsity: 0.9
precision:
dit: fp32
pipeline:
flow_shift: 3.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 480
num_width: 832
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 2.0e-06
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: wan_t2v_distill_dmd_VSA
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.5
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -47,6 +47,83 @@ OUTPUT_DIR="checkpoints/wan_t2v_finetune"
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name wan_t2v_distill_dmd
--output_dir "$OUTPUT_DIR"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 21
--num_height 480
--num_width 832
--num_frames 81
--enable_gradient_checkpointing_type "full"
)
# Parallel arguments
parallel_args=(
--num_gpus 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 64
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 2e-6
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 8
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3.5
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -54,10 +131,11 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.1-T2V/Wan-Syn-Data-480P/distill_dmd_t2v_1.3B.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "$OUTPUT_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,53 +0,0 @@
# Read by distill_dmd_t2v_1.3B.slurm, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, training.checkpoint.output_dir, and
# training.validation.dataset_file on the command line.
engine:
num_gpus: 64
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 64
hsdp_shard_dim: 1
precision:
dit: fp32
pipeline:
flow_shift: 8.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 480
num_width: 832
num_frames: 81
train_batch_size: 1
num_latent_t: 21
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 2.0e-06
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: wan_t2v_distill_dmd
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.5
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -8,4 +8,4 @@ uv pip install vsa
```
### Data-free Distillation
When `training.distillation.simulate_generator_forward` is enabled, distillation becomes data-free by simulating intermediate steps through forward inference of the generator. This helps avoid training–inference mismatch. See Section 4.5 of [DMD2](https://arxiv.org/pdf/2405.14867) for details.
When `--simulate_generator_forward` is enabled, distillation becomes data-free by simulating intermediate steps through forward inference of the generator. This helps avoid training–inference mismatch. See Section 4.5 of [DMD2](https://arxiv.org/pdf/2405.14867) for details.
@@ -48,6 +48,90 @@ OUTPUT_DIR="checkpoints/wan_t2v_finetune"
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name Wan_distillation
--output_dir "$OUTPUT_DIR"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 31
--num_height 704
--num_width 1280
--num_frames 121
--enable_gradient_checkpointing_type "full"
)
# Parallel arguments
parallel_args=(
--num_gpus 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 64
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DIR"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 4e-6
--lr_scheduler "cosine_with_min_lr"
--min_lr_ratio 0.5
--lr_warmup_steps 100
--fake_score_learning_rate 2e-6
--fake_score_lr_scheduler "cosine_with_min_lr"
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 200
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 5
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3
--simulate_generator_forward
--log_visualization # disable if oom
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -55,10 +139,11 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.2-TI2V-5B-Diffusers/Data-free/distill_dmd_t2v_5B.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "$OUTPUT_DIR" \
--training.validation.dataset_file "$VALIDATION_DIR"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,60 +0,0 @@
# Read by distill_dmd_t2v_5B.sh, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, training.checkpoint.output_dir, and
# training.validation.dataset_file on the command line.
engine:
num_gpus: 64
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 64
hsdp_shard_dim: 1
precision:
dit: fp32
pipeline:
flow_shift: 5.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 704
num_width: 1280
num_frames: 121
train_batch_size: 1
num_latent_t: 31
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 4.0e-06
weight_decay: 0.01
lr_scheduler: cosine_with_min_lr
lr_warmup_steps: 100
min_lr_ratio: 0.5
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 200
checkpoints_total_limit: 3
tracker:
project_name: Wan_distillation
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
log_visualization: true # disable if oom
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 2.0e-06
fake_score_lr_scheduler: cosine_with_min_lr
simulate_generator_forward: true
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -47,6 +47,91 @@ VALIDATION_DIR=your_validation_path #(example:validation_64.json)
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name Wan_distillation
--output_dir "your_output_dir"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 31
--num_height 704
--num_width 1280
--num_frames 121
--enable_gradient_checkpointing_type "full"
)
# Parallel arguments
parallel_args=(
--num_gpus 64
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 64
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DIR"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 4e-6
--lr_scheduler "cosine_with_min_lr"
--min_lr_ratio 0.5
--lr_warmup_steps 100
--fake_score_learning_rate 2e-6
--fake_score_lr_scheduler "cosine_with_min_lr"
--mixed_precision "bf16"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 200
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 5
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3
--simulate_generator_forward
--log_visualization # disable if oom
--VSA_sparsity 0.8
)
srun torchrun \
--nnodes $SLURM_JOB_NUM_NODES \
--nproc_per_node $NUM_GPUS \
@@ -54,9 +139,11 @@ srun torchrun \
--rdzv_backend=c10d \
--rdzv_endpoint="$MASTER_ADDR:$MASTER_PORT" \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.2-TI2V-5B-Diffusers/Data-free/distill_dmd_t2v_5B_VSA.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.validation.dataset_file "$VALIDATION_DIR"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,63 +0,0 @@
# Read by distill_dmd_t2v_5B_VSA.sh, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, and training.validation.dataset_file on the
# command line.
engine:
num_gpus: 64
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 64
hsdp_shard_dim: 1
attention:
vsa_sparsity: 0.8
precision:
dit: fp32
pipeline:
flow_shift: 5.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 704
num_width: 1280
num_frames: 121
train_batch_size: 1
num_latent_t: 31
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 4.0e-06
weight_decay: 0.01
lr_scheduler: cosine_with_min_lr
lr_warmup_steps: 100
min_lr_ratio: 0.5
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
output_dir: your_output_dir
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 200
checkpoints_total_limit: 3
tracker:
project_name: Wan_distillation
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
log_visualization: true # disable if oom
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.0
fake_score_learning_rate: 2.0e-06
fake_score_lr_scheduler: cosine_with_min_lr
simulate_generator_forward: true
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -22,15 +22,94 @@ OUTPUT_DIR="checkpoints/wan_t2v_finetune"
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name wan_t2v_distill_dmd_VSA
--output_dir "$OUTPUT_DIR"
--max_train_steps 4000
--train_batch_size 1
--train_sp_batch_size 1
--gradient_accumulation_steps 1
--num_latent_t 31
--num_height 704
--num_width 1280
--num_frames 121
--enable_gradient_checkpointing_type "full"
--training_state_checkpointing_steps 500
--weight_only_checkpointing_steps 500
)
# Parallel arguments
parallel_args=(
--num_gpus 1
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate 2e-6
--mixed_precision "bf16"
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 8
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3.5
--VSA_sparsity 0.8
)
torchrun \
--nnodes 1 \
--nproc_per_node $NUM_GPUS \
--master_port $MASTER_PORT \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.2-TI2V-5B-Diffusers/crush_smol/distill_dmd_VSA_t2v_5B.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.checkpoint.output_dir "$OUTPUT_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,55 +0,0 @@
# Read by distill_dmd_VSA_t2v_5B.sh, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, training.checkpoint.output_dir, and
# training.validation.dataset_file on the command line.
engine:
num_gpus: 1
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1
hsdp_shard_dim: 1
attention:
vsa_sparsity: 0.8
precision:
dit: fp32
pipeline:
flow_shift: 8.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 704
num_width: 1280
num_frames: 121
train_batch_size: 1
num_latent_t: 31
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 2.0e-06
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: wan_t2v_distill_dmd_VSA
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.5
ema:
start_step: 0
model:
enable_gradient_checkpointing_type: full
@@ -21,14 +21,96 @@ VALIDATION_DATASET_FILE="examples/distill/Wan2.2-TI2V-5B-Diffusers/crush_smol/va
# export CUDA_VISIBLE_DEVICES=4,5
# IP=[MASTER NODE IP]
# Training arguments
training_args=(
--tracker_project_name wan_t2v_distill_dmd_VSA
--output_dir="checkpoints/wan_t2v_finetune"
--max_train_steps=4000
--train_batch_size=1
--train_sp_batch_size 1
--gradient_accumulation_steps=1
--num_latent_t 31
--num_height 704
--num_width 1280
--num_frames 121
--enable_gradient_checkpointing_type "full"
--training_state_checkpointing_steps=500
--weight_only_checkpointing_steps=500
--lora_rank 32
--lora_training True
)
# Parallel arguments
parallel_args=(
--num_gpus 1
--sp_size 1
--tp_size 1
--hsdp_replicate_dim 1
--hsdp_shard_dim 1
)
# Model arguments
model_args=(
--model_path $MODEL_PATH
--pretrained_model_name_or_path $MODEL_PATH
--real_score_model_path $REAL_SCORE_MODEL_PATH
--fake_score_model_path $FAKE_SCORE_MODEL_PATH
)
# Dataset arguments
dataset_args=(
--data_path "$DATA_DIR"
--dataloader_num_workers 4
)
# Validation arguments
validation_args=(
--log_validation
--validation_dataset_file "$VALIDATION_DATASET_FILE"
--validation_steps 200
--validation_sampling_steps "3"
--validation_guidance_scale "6.0" # not used for dmd inference
)
# Optimizer arguments
optimizer_args=(
--learning_rate=1e-4
--mixed_precision="bf16"
--weight_decay 0.01
--max_grad_norm 1.0
)
# Miscellaneous arguments
miscellaneous_args=(
--inference_mode False
--checkpoints_total_limit 3
--training_cfg_rate 0.0
--dit_precision "fp32"
--ema_start_step 0
--flow_shift 8
--seed 1000
)
# DMD arguments
dmd_args=(
--dmd_denoising_steps '1000,757,522'
--min_timestep_ratio 0.02
--max_timestep_ratio 0.98
--generator_update_interval 5
--real_score_guidance_scale 3.5
--VSA_sparsity 0.8
)
torchrun \
--nnodes 1 \
--nproc_per_node $NUM_GPUS \
--master_port $MASTER_PORT \
fastvideo/training/wan_distillation_pipeline.py \
--config examples/distill/Wan2.2-TI2V-5B-Diffusers/crush_smol/distill_dmd_VSA_t2v_5B_lora.yaml \
--model_path "$MODEL_PATH" \
--training.distillation.real_score_model_path "$REAL_SCORE_MODEL_PATH" \
--training.distillation.fake_score_model_path "$FAKE_SCORE_MODEL_PATH" \
--training.data.data_path "$DATA_DIR" \
--training.validation.dataset_file "$VALIDATION_DATASET_FILE"
"${parallel_args[@]}" \
"${model_args[@]}" \
"${dataset_args[@]}" \
"${training_args[@]}" \
"${optimizer_args[@]}" \
"${validation_args[@]}" \
"${miscellaneous_args[@]}" \
"${dmd_args[@]}"
@@ -1,59 +0,0 @@
# Read by distill_dmd_VSA_t2v_5B_lora.sh, which passes model_path, training.distillation.real_score_model_path,
# training.distillation.fake_score_model_path, training.data.data_path, and training.validation.dataset_file on the
# command line.
engine:
num_gpus: 1
parallelism:
tp_size: 1
sp_size: 1
hsdp_replicate_dim: 1
hsdp_shard_dim: 1
attention:
vsa_sparsity: 0.8
precision:
dit: fp32
pipeline:
flow_shift: 8.0
dmd_denoising_steps: [1000, 757, 522]
training:
data:
dataloader_num_workers: 4
num_height: 704
num_width: 1280
num_frames: 121
train_batch_size: 1
num_latent_t: 31
training_cfg_rate: 0.0
seed: 1000
train_sp_batch_size: 1
optimizer:
learning_rate: 0.0001
weight_decay: 0.01
max_grad_norm: 1.0
loop:
max_train_steps: 4000
gradient_accumulation_steps: 1
checkpoint:
output_dir: checkpoints/wan_t2v_finetune
training_state_checkpointing_steps: 500
weight_only_checkpointing_steps: 500
checkpoints_total_limit: 3
tracker:
project_name: wan_t2v_distill_dmd_VSA
validation:
enabled: true
sampling_steps: [3]
guidance_scale: 6.0 # not used for dmd inference
every_steps: 200
distillation:
generator_update_interval: 5
min_timestep_ratio: 0.02
max_timestep_ratio: 0.98
real_score_guidance_scale: 3.5
ema:
start_step: 0
lora:
enabled: true
rank: 32
model:
enable_gradient_checkpointing_type: full
@@ -8,7 +8,17 @@ OUTPUT_DIR="data/crush-smol_processed_ti2v/"
torchrun --nproc_per_node=$GPU_NUM \
fastvideo/pipelines/preprocess/v1_preprocess.py \
--config examples/distill/Wan2.2-TI2V-5B-Diffusers/crush_smol/preprocess_wan_data_ti2v_5b.yaml \
--model_path $MODEL_PATH \
--preprocess.data_merge_path $DATA_MERGE_PATH \
--preprocess.dataset_output_dir=$OUTPUT_DIR
--data_merge_path $DATA_MERGE_PATH \
--preprocess_video_batch_size 8 \
--seed 42 \
--max_height 704 \
--max_width 1280 \
--num_frames 121 \
--dataloader_num_workers 0 \
--output_dir=$OUTPUT_DIR \
--train_fps 24 \
--samples_per_file 8 \
--flush_frequency 8 \
--video_length_tolerance_range 5 \
--preprocess_task "t2v"
@@ -1,20 +0,0 @@
# Read by preprocess_wan_data_ti2v_5b.sh, which passes model_path, preprocess.data_merge_path, and
# preprocess.dataset_output_dir.
# Offload settings of the per-task preprocessing pipelines (v1_preprocess.py).
engine:
offload:
dit_layerwise: true
image_encoder: true
pin_cpu_memory: true
preprocess:
preprocess_video_batch_size: 8
seed: 42
max_height: 704
max_width: 1280
num_frames: 121
dataloader_num_workers: 0
train_fps: 24
samples_per_file: 8
flush_frequency: 8
video_length_tolerance_range: 5
preprocess_task: t2v
+2 -2
View File
@@ -177,13 +177,13 @@ from fastvideo import VideoGenerator
def main():
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
{"engine": {"num_gpus": 1}},
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({"prompt": prompt})
video = generator.generate_video(prompt)
if __name__ == "__main__":
main()
+15 -27
View File
@@ -1,5 +1,7 @@
from fastvideo import VideoGenerator
# from fastvideo.api.sampling_param import SamplingParam
OUTPUT_PATH = "video_samples"
@@ -11,35 +13,24 @@ def main():
generator = VideoGenerator.from_pretrained(
"Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
# FastVideo will automatically handle distributed setup
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"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,
},
},
},
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,
)
# 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({
"prompt": prompt,
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
# video = generator.generate({
# "prompt": prompt,
# "inputs": {"image_path": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg"},
# "sampling": {"num_frames": 45},
# "output": {"output_path": "wan_t2v_videos/"},
# })
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/")
# Generate another video with a different prompt, without reloading the
# model!
@@ -48,10 +39,7 @@ 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({
"prompt": prompt2,
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True)
if __name__ == "__main__":
+17 -18
View File
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
def main():
@@ -8,20 +9,16 @@ def main():
generator = VideoGenerator.from_pretrained(
model_path,
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set True if GPU is out of memory
"offload": {
"dit": False,
"vae": False,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
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)
# image2world example from official repo
image_path = "assets/images/bus_terminal.jpg"
@@ -35,12 +32,14 @@ def main():
"It then comes to a smooth stop, resuming its position in line. "
"Overhead signage in Chinese characters remains illuminated, enhancing the vibrant, urban night scene.")
generator.generate({
"prompt": prompt,
"inputs": {"image_path": str(image_path)},
"output": {"output_path": "outputs_video/cosmos2_5_i2w.mp4", "save_video": True},
"extensions": {"num_cond_frames": 1},
})
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.shutdown()
+16 -16
View File
@@ -1,4 +1,5 @@
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
def main():
@@ -7,20 +8,17 @@ def main():
generator = VideoGenerator.from_pretrained(
model_path,
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set True if GPU is out of memory
"offload": {
"dit": False,
"vae": False,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
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,
)
# 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. "
@@ -35,10 +33,12 @@ def main():
"The scene retains its industrial ambiance, with the welding sparks and smoke dominating the visual field, "
"underscoring the ongoing nature of the welding operation.")
generator.generate({
"prompt": prompt,
"output": {"output_path": "outputs_video/cosmos2_5_t2w.mp4", "save_video": True},
})
generator.generate_video(
prompt,
sampling_param=sampling_param,
output_path="outputs_video/cosmos2_5_t2w.mp4",
save_video=True,
)
generator.shutdown()
+17 -18
View File
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
def main():
@@ -8,20 +9,16 @@ def main():
generator = VideoGenerator.from_pretrained(
model_path,
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set True if GPU is out of memory
"offload": {
"dit": False,
"vae": False,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
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)
# video2world example from official repo
video_path = "assets/videos/robot_pouring.mp4"
@@ -39,12 +36,14 @@ 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({
"prompt": prompt,
"inputs": {"video_path": str(video_path)},
"output": {"output_path": "outputs_video/cosmos2_5_v2w.mp4", "save_video": True},
"extensions": {"num_cond_frames": 1},
})
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.shutdown()
+19 -26
View File
@@ -2,6 +2,8 @@ import os
import time
from fastvideo import VideoGenerator
from fastvideo.api.sampling_param import SamplingParam
OUTPUT_PATH = "video_samples_dmd2"
@@ -10,34 +12,29 @@ def main():
load_start_time = time.perf_counter()
model_name = "FastVideo/FastWan2.1-T2V-1.3B-Diffusers"
generator = VideoGenerator.from_config({
"model_path": model_name,
"engine": {
# 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": {
"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,
},
"attention": {"vsa_sparsity": 0.8},
},
})
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,
)
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({
"prompt": prompt,
"sampling": {"num_frames": 81},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, sampling_param=sampling_param)
end_time = time.perf_counter()
gen_time = end_time - start_time
@@ -49,11 +46,7 @@ def main():
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
"cinematic.")
start_time = time.perf_counter()
video2 = generator.generate({
"prompt": prompt2,
"sampling": {"num_frames": 81},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, num_frames=81)
end_time = time.perf_counter()
gen_time2 = end_time - start_time
@@ -3,12 +3,12 @@ import time
from fastvideo import VideoGenerator
from fastvideo.api import (
AttentionConfig,
EngineConfig,
GenerationRequest,
GeneratorConfig,
OffloadConfig,
OutputConfig,
PipelineSelection,
)
OUTPUT_PATH = "video_samples_dmd2_typed"
@@ -29,8 +29,12 @@ def main():
dit=False,
vae=False,
),
attention=AttentionConfig(vsa_sparsity=0.8),
),
# PR 2 still routes a few advanced inference knobs through the
# compatibility bridge until they get first-class typed fields.
pipeline=PipelineSelection(experimental={
"VSA_sparsity": 0.8,
}, ),
)
load_start_time = time.perf_counter()
+23 -34
View File
@@ -15,20 +15,16 @@ 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_config({
"model_path": model_name,
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {"components": {"override_pipeline_cls_name": "DreamXWorldPipeline"}},
})
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",
)
prompt = os.getenv(
"DREAMX_WORLD_PROMPT",
@@ -40,30 +36,23 @@ def main():
"https://huggingface.co/datasets/YiYiXu/testing-images/resolve/main/wan_i2v_input.JPG",
)
request = {
"prompt": prompt,
"sampling": {
"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": {
"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(",")],
},
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:
request["inputs"] = {"image_path": image_path}
kwargs["image_path"] = image_path
try:
generator.generate(request)
generator.generate_video(prompt, **kwargs)
finally:
generator.shutdown()
+18 -15
View File
@@ -25,13 +25,11 @@ from pathlib import Path
from fastvideo import VideoGenerator
from fastvideo.api import (
AttentionConfig,
CompileConfig,
ComponentConfig,
EngineConfig,
GenerationRequest,
GeneratorConfig,
MiniMaxH3Options,
OffloadConfig,
OutputConfig,
ParallelismConfig,
@@ -256,6 +254,23 @@ def _execution_backend(args: argparse.Namespace) -> str:
def build_generator_config(args: argparse.Namespace) -> GeneratorConfig:
use_vsa = _uses_vsa(args)
experimental: dict[str, object] = {
"attention_backend": "VIDEO_SPARSE_ATTN_H3" if use_vsa else "FLASH_ATTN",
"inference_torch_compile": args.inference_torch_compile,
"vae_parallel_decode": args.parallel_vae,
"vae_parallel_decode_strategy": "gather",
}
if args.h3_sequential_load is not None:
experimental["h3_sequential_load"] = args.h3_sequential_load
if args.video_decode_backend != "h3-vae":
experimental["video_decode_backend"] = args.video_decode_backend
if args.taeh3_checkpoint is not None:
experimental["taeh3_checkpoint"] = args.taeh3_checkpoint
if use_vsa:
experimental.update({
"VSA_sparsity": args.vsa_sparsity,
"VSA_tile_size": args.vsa_tile_size,
})
return GeneratorConfig(
model_path=args.model_path,
pipeline=PipelineSelection(
@@ -263,13 +278,7 @@ def build_generator_config(args: argparse.Namespace) -> GeneratorConfig:
lora_path=getattr(args, "lora_path", None),
lora_strength=float(getattr(args, "lora_strength", 1.0)),
),
model=MiniMaxH3Options(
sequential_load=args.h3_sequential_load,
video_decode_backend=None if args.video_decode_backend == "h3-vae" else args.video_decode_backend,
taeh3_checkpoint=args.taeh3_checkpoint,
vae_parallel_decode=args.parallel_vae,
vae_parallel_decode_strategy="gather",
),
experimental=experimental,
),
engine=EngineConfig(
num_gpus=args.num_gpus,
@@ -288,12 +297,6 @@ def build_generator_config(args: argparse.Namespace) -> GeneratorConfig:
enabled=args.torch_compile,
mode=args.compile_mode,
vae_enabled=args.compile_vae,
regional=args.inference_torch_compile,
),
attention=AttentionConfig(
backend="VIDEO_SPARSE_ATTN_H3" if use_vsa else "FLASH_ATTN",
vsa_sparsity=args.vsa_sparsity if use_vsa else None,
vsa_tile_size=args.vsa_tile_size if use_vsa else None,
),
),
)
@@ -17,13 +17,11 @@ sys.path.insert(0, str(FASTVIDEO_ROOT))
from fastvideo import VideoGenerator # noqa: E402
from fastvideo.api import ( # noqa: E402
AttentionConfig,
CompileConfig,
ComponentConfig,
EngineConfig,
GenerationRequest,
GeneratorConfig,
MiniMaxH3Options,
OffloadConfig,
OutputConfig,
ParallelismConfig,
@@ -59,7 +57,17 @@ def main() -> None:
args = parse_args()
attention_backend = VARIANT_BACKENDS[args.variant]
use_vsa = attention_backend == "VIDEO_SPARSE_ATTN_H3"
experimental = {
"attention_backend": attention_backend,
"inference_torch_compile": attention_backend == "FLASH_ATTN",
"vae_parallel_decode": True,
"vae_parallel_decode_strategy": "gather",
}
if attention_backend == "VIDEO_SPARSE_ATTN_H3":
experimental.update({
"VSA_sparsity": 0.9,
"VSA_tile_size": 64,
})
adapter_path = hf_hub_download(
repo_id="FastVideo/FastVideo-FastH3-4-step-Preview-v1-LoRA",
@@ -73,18 +81,13 @@ def main() -> None:
model_path="MiniMaxAI/MiniMax-H3",
pipeline=PipelineSelection(
components=ComponentConfig(lora_path=adapter_path, lora_strength=1.0),
model=MiniMaxH3Options(vae_parallel_decode=True, vae_parallel_decode_strategy="gather"),
experimental=experimental,
),
engine=EngineConfig(
num_gpus=4,
parallelism=ParallelismConfig(tp_size=1, sp_size=4),
offload=OffloadConfig(dit=False, dit_layerwise=False),
compile=CompileConfig(vae_enabled=True, regional=attention_backend == "FLASH_ATTN"),
attention=AttentionConfig(
backend=attention_backend,
vsa_sparsity=0.9 if use_vsa else None,
vsa_tile_size=64 if use_vsa else None,
),
compile=CompileConfig(vae_enabled=True),
),
)
)
@@ -11,13 +11,11 @@ from huggingface_hub import hf_hub_download
from fastvideo import VideoGenerator
from fastvideo.api import (
AttentionConfig,
CompileConfig,
ComponentConfig,
EngineConfig,
GenerationRequest,
GeneratorConfig,
MiniMaxH3Options,
OffloadConfig,
OutputConfig,
ParallelismConfig,
@@ -52,7 +50,17 @@ def main() -> None:
output_dir.mkdir(parents=True, exist_ok=True)
attention_backend = VARIANT_BACKENDS[args.variant]
use_vsa = attention_backend == "VIDEO_SPARSE_ATTN_H3"
experimental = {
"attention_backend": attention_backend,
"inference_torch_compile": attention_backend == "FLASH_ATTN",
"vae_parallel_decode": True,
"vae_parallel_decode_strategy": "gather",
}
if attention_backend == "VIDEO_SPARSE_ATTN_H3":
experimental.update({
"VSA_sparsity": 0.9,
"VSA_tile_size": 64,
})
adapter_path = hf_hub_download(
repo_id="FastVideo/FastVideo-FastH3-4-step-Preview-v1-LoRA",
@@ -63,18 +71,13 @@ def main() -> None:
model_path="MiniMaxAI/MiniMax-H3",
pipeline=PipelineSelection(
components=ComponentConfig(lora_path=adapter_path, lora_strength=1.0),
model=MiniMaxH3Options(vae_parallel_decode=True, vae_parallel_decode_strategy="gather"),
experimental=experimental,
),
engine=EngineConfig(
num_gpus=4,
parallelism=ParallelismConfig(tp_size=1, sp_size=4),
offload=OffloadConfig(dit=False, dit_layerwise=False),
compile=CompileConfig(vae_enabled=True, regional=attention_backend == "FLASH_ATTN"),
attention=AttentionConfig(
backend=attention_backend,
vsa_sparsity=0.9 if use_vsa else None,
vsa_tile_size=64 if use_vsa else None,
),
compile=CompileConfig(vae_enabled=True),
quantization=QuantizationConfig(transformer_quant="MXFP8"),
),
)
@@ -30,16 +30,14 @@ generator:
compile:
enabled: false
vae_enabled: true
regional: true
attention:
backend: VIDEO_SPARSE_ATTN_H3
vsa_sparsity: 0.9
vsa_tile_size: 64
pipeline:
model:
minimax_h3:
vae_parallel_decode: true
vae_parallel_decode_strategy: gather
experimental:
attention_backend: VIDEO_SPARSE_ATTN_H3
VSA_sparsity: 0.9
VSA_tile_size: 64
inference_torch_compile: true
vae_parallel_decode: true
vae_parallel_decode_strategy: gather
request:
prompt: >-
A wide cinematic shot of an alpine meadow at sunrise, pale pink mountain
@@ -30,16 +30,14 @@ generator:
compile:
enabled: false
vae_enabled: true
regional: true
attention:
backend: VIDEO_SPARSE_ATTN_H3
vsa_sparsity: 0.9
vsa_tile_size: 64
pipeline:
model:
minimax_h3:
vae_parallel_decode: true
vae_parallel_decode_strategy: gather
experimental:
attention_backend: VIDEO_SPARSE_ATTN_H3
VSA_sparsity: 0.9
VSA_tile_size: 64
inference_torch_compile: true
vae_parallel_decode: true
vae_parallel_decode_strategy: gather
request:
prompt: >-
A wide cinematic shot of an alpine meadow at sunrise, pale pink mountain
+31 -32
View File
@@ -83,23 +83,24 @@ def main() -> None:
os.makedirs(args.out_dir, exist_ok=True)
generator = VideoGenerator.from_config({
"model_path": args.model_path,
"engine": {
"num_gpus": args.num_gpus,
"parallelism": {"sp_size": 1, "tp_size": 1},
"offload": {
"dit": False,
"dit_layerwise": False,
"text_encoder": False,
"vae": False,
"image_encoder": False,
"pin_cpu_memory": False,
},
"use_fsdp_inference": False,
},
"pipeline": {"workload_type": "t2i"},
})
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,
)
try:
for i, prompt in enumerate(prompts):
seed = args.seed + i
@@ -108,23 +109,21 @@ def main() -> None:
output_path = os.path.join(args.out_dir, f"{filename_base}.png")
print(f"[flux] prompt_idx={i} seed={seed} output_path={output_path}")
request = {
"prompt": prompt,
"sampling": {
"height": args.height,
"width": args.width,
"num_frames": 1,
"fps": 1,
"num_inference_steps": args.steps,
"guidance_scale": args.guidance,
"use_embedded_guidance": True,
"true_cfg_scale": 1.0,
"seed": seed,
},
"output": {"output_path": output_path, "save_video": True},
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,
"use_embedded_guidance": True,
"true_cfg_scale": 1.0,
"seed": seed,
"save_video": True,
}
generator.generate(request)
generator.generate_video(prompt, **generation_kwargs)
print(f"[flux] done. outputs written to: {args.out_dir}")
finally:
+22 -29
View File
@@ -56,18 +56,12 @@ def main():
# FastVideo will automatically download weights from HuggingFace
generator = VideoGenerator.from_pretrained(
MODEL_PATH,
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": True,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
num_gpus=1,
use_fsdp_inference=True,
dit_cpu_offload=True,
vae_cpu_offload=True,
text_encoder_cpu_offload=True,
pin_cpu_memory=True,
)
# Video parameters
@@ -101,24 +95,23 @@ 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]}...")
request = {
"prompt": prompt,
"negative_prompt": "",
"sampling": {
"height": height,
"width": width,
"num_frames": num_frames,
"num_inference_steps": 50,
"guidance_scale": 6.0,
"seed": 42,
"fps": 24,
},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
"extensions": {"camera_states": camera_states},
}
gen_kw = dict(
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,
)
if is_i2v:
request["inputs"] = {"image_path": image_path}
generator.generate(request)
gen_kw["image_path"] = image_path
generator.generate_video(**gen_kw)
if __name__ == "__main__":
+23 -32
View File
@@ -60,40 +60,31 @@ def main():
generator = VideoGenerator.from_pretrained(
args.model_path,
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": True,
},
},
},
num_gpus=1,
use_fsdp_inference=False,
dit_cpu_offload=False,
vae_cpu_offload=True,
text_encoder_cpu_offload=True,
pin_cpu_memory=True,
)
video = generator.generate({
"prompt": args.prompt,
"negative_prompt": args.negative_prompt,
"inputs": {"image_path": args.image_path},
"sampling": {
"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": {"output_path": args.output_path, "save_video": True},
"extensions": {
"trajectory_type": args.trajectory,
"movement_distance": args.movement_distance,
"camera_rotation": args.camera_rotation,
},
})
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,
)
generator.shutdown()
+19 -25
View File
@@ -13,31 +13,25 @@ def main():
generator = VideoGenerator.from_pretrained(
"hunyuanvideo-community/HunyuanVideo-1.5-Diffusers-480p_t2v",
# FastVideo will automatically handle distributed setup
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"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,
},
},
},
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,
)
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({
"prompt": prompt,
"negative_prompt": "",
"sampling": {"num_frames": 81, "fps": 16},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video = generator.generate_video(prompt,
output_path=OUTPUT_PATH,
save_video=True,
negative_prompt="",
num_frames=81,
fps=16)
prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame "
"glistening under the warm afternoon sun. The tall grass ripples gently in "
@@ -45,12 +39,12 @@ def main():
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
"cinematic.")
video2 = generator.generate({
"prompt": prompt2,
"negative_prompt": "",
"sampling": {"num_frames": 81, "fps": 16},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video2 = generator.generate_video(prompt2,
output_path=OUTPUT_PATH,
save_video=True,
negative_prompt="",
num_frames=81,
fps=16)
if __name__ == "__main__":
+9 -23
View File
@@ -14,30 +14,20 @@ def main():
"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
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"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,
},
},
},
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,
)
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({
"prompt": prompt,
"negative_prompt": "",
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video = generator.generate_video(prompt, output_path=OUTPUT_PATH, save_video=True, negative_prompt="")
prompt2 = ("A majestic lion strides across the golden savanna, its powerful frame "
"glistening under the warm afternoon sun. The tall grass ripples gently in "
@@ -45,11 +35,7 @@ def main():
"embodying the raw energy of the wild. Low angle, steady tracking shot, "
"cinematic.")
video2 = generator.generate({
"prompt": prompt2,
"negative_prompt": "",
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
video2 = generator.generate_video(prompt2, output_path=OUTPUT_PATH, save_video=True, negative_prompt="")
if __name__ == "__main__":
+20 -29
View File
@@ -35,40 +35,31 @@ def main():
print("\nInitializing VideoGenerator for HYWorld...")
generator = VideoGenerator.from_pretrained(
"FastVideo/HY-WorldPlay-Bidirectional-Diffusers",
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": True,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": True,
"image_encoder": True,
},
},
},
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,
)
# Generate video
# The pose string is automatically converted to camera matrices by the pipeline
print("\nGenerating video...")
generator.generate({
"prompt": args.prompt,
"negative_prompt": "",
"inputs": {
"image_path": args.image,
"pose": args.pose, # Camera trajectory control
},
"sampling": {
"num_frames": args.num_frames,
"fps": 24,
"height": HEIGHT,
"width": WIDTH,
"seed": args.seed,
},
"output": {"output_path": args.output_path, "save_video": True},
})
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,
)
print(f"\nVideo saved to: {args.output_path}")
@@ -10,28 +10,25 @@ def main():
"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": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"dit": False,
"vae": False,
"text_encoder": True,
"pin_cpu_memory": True,
# "image_encoder": False,
},
},
},
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,
)
prompt = ("A woman stands up and walks away")
_ = generator.generate({
"prompt": prompt,
"inputs": {"image_path": IMAGE_PATH},
"sampling": {"height": 1024, "width": 1024, "num_frames": 121},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
_ = generator.generate_video(
prompt,
image_path=IMAGE_PATH,
output_path=OUTPUT_PATH,
save_video=True,
height=1024,
width=1024,
num_frames=121,
)
if __name__ == "__main__":
@@ -9,40 +9,36 @@ def main():
# "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": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"dit": False,
"vae": False,
"text_encoder": True,
"pin_cpu_memory": True,
# "image_encoder": False,
},
},
},
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,
)
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({
"prompt": prompt,
"sampling": {"height": 512, "width": 768, "num_frames": 121},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
_ = generator.generate_video(prompt,
output_path=OUTPUT_PATH,
save_video=True,
height=512,
width=768,
num_frames=121)
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.")
_ = generator.generate({
"prompt": prompt2,
"sampling": {"height": 512, "width": 768, "num_frames": 121},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
_ = generator.generate_video(prompt2,
output_path=OUTPUT_PATH,
save_video=True,
height=512,
width=768,
num_frames=121)
if __name__ == "__main__":
@@ -29,17 +29,11 @@ def main() -> None:
args = parse_args()
generator = VideoGenerator.from_pretrained(
str(args.model_path),
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False,
"offload": {
"text_encoder": True,
"vae": False,
"pin_cpu_memory": True,
},
},
},
num_gpus=1,
use_fsdp_inference=False,
text_encoder_cpu_offload=True,
vae_cpu_offload=False,
pin_cpu_memory=True,
)
try:
generator.generate({
@@ -13,43 +13,36 @@ OUTPUT_PATH = REPO_ROOT / "outputs" / "lingbotworld2_causal_fast.mp4"
def main() -> None:
"""Load the native FastVideo LingBot World 2 causal-fast pipeline and generate one video."""
generator = VideoGenerator.from_config({
"model_path": os.environ["LINGBOTWORLD2_MODEL_PATH"],
"engine": {
"num_gpus": 8,
"parallelism": {"sp_size": 8, "hsdp_shard_dim": 8},
"use_fsdp_inference": True,
"offload": {
"dit_layerwise": False,
"dit": False,
"vae": False,
"text_encoder": False,
"pin_cpu_memory": True,
},
},
"pipeline": {"components": {"override_pipeline_cls_name": "LingBotWorld2CausalFastPipeline"}},
})
generator = VideoGenerator.from_pretrained(
os.environ["LINGBOTWORLD2_MODEL_PATH"],
num_gpus=8,
sp_size=8,
hsdp_shard_dim=8,
use_fsdp_inference=True,
dit_layerwise_offload=False,
dit_cpu_offload=False,
vae_cpu_offload=False,
text_encoder_cpu_offload=False,
pin_cpu_memory=True,
override_pipeline_cls_name="LingBotWorld2CausalFastPipeline",
)
try:
generator.generate({
"prompt":
generator.generate_video(
"A serene lakeside scene with a lone tree standing in calm water, surrounded by distant snow-capped mountains under a bright blue sky with drifting white clouds; gentle ripples reflect the tree and sky, creating a tranquil, meditative atmosphere.",
"negative_prompt": "",
"inputs": {
"image_path": str(DATASET_DIR / "image.jpg"),
"action_path": str(DATASET_DIR),
},
"sampling": {
"height": 480,
"width": 832,
"num_frames": 65,
"num_inference_steps": 4,
"guidance_scale": 1.0,
"fps": 16,
"seed": 42,
},
"output": {"output_path": str(OUTPUT_PATH), "save_video": True},
})
image_path=str(DATASET_DIR / "image.jpg"),
action_path=str(DATASET_DIR),
output_path=str(OUTPUT_PATH),
save_video=True,
height=480,
width=832,
num_frames=65,
num_inference_steps=4,
guidance_scale=1.0,
negative_prompt="",
fps=16,
seed=42,
)
finally:
generator.shutdown()
@@ -13,20 +13,14 @@ def main():
generator = VideoGenerator.from_pretrained(
"FastVideo/LingBot-World-Base-Cam-Diffusers",
# FastVideo will automatically handle distributed setup
{
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"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,
},
},
},
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,
)
num_frames = 81
@@ -41,12 +35,16 @@ def main():
spatial_scale=8,
)
generator.generate({
"prompt": prompt,
"inputs": {"image_path": image_path, "c2ws_plucker_emb": c2ws_plucker_emb},
"sampling": {"num_frames": num_frames, "height": 480, "width": 832},
"output": {"output_path": OUTPUT_PATH, "save_video": True},
})
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,
)
if __name__ == "__main__":
+81 -110
View File
@@ -48,38 +48,33 @@ def basic_generation():
print("LongCat I2V: Basic Generation (50 steps, 480p)")
print("=" * 60)
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-I2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {"model": {"longcat": {"enable_bsa": False}}},
})
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,
)
output_path = "outputs_video/longcat_i2v_basic"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"image_path": IMAGE_PATH},
"sampling": {
"height": 480,
"width": 480, # Square
"num_frames": 93,
"num_inference_steps": 50,
"fps": 15,
"guidance_scale": 4.0,
"seed": SEED,
},
"output": {"output_path": output_path, "save_video": True},
})
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,
)
print(f"\nBasic generation complete! Video saved to: {output_path}")
generator.shutdown()
@@ -100,44 +95,35 @@ def distill_refine_generation():
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
print("-" * 40)
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-I2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {"longcat": {"enable_bsa": False}},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA",
"lora_nickname": "distilled",
},
},
})
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",
)
distill_output_path = "outputs_video/longcat_i2v_distill"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"image_path": IMAGE_PATH},
"sampling": {
"height": 480,
"width": 480, # Square
"num_frames": 93,
"num_inference_steps": 16,
"fps": 15,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"output_path": distill_output_path, "save_video": True},
})
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,
)
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
generator.shutdown()
@@ -158,55 +144,40 @@ def distill_refine_generation():
# 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_config({
"model_path": "FastVideo/LongCat-Video-T2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": True,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {
"longcat": {
"enable_bsa": True,
"bsa_sparsity": 0.875,
"bsa_chunk_q": [4, 4, 4],
"bsa_chunk_k": [4, 4, 4],
},
},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA",
"lora_nickname": "refinement",
},
},
})
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_output_path = "outputs_video/longcat_i2v_refine_720p"
refine_generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"refine_from": distill_video_path},
"sampling": {
"height": 720,
"width": 720,
"num_inference_steps": 50,
"fps": 30,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"output_path": refine_output_path, "save_video": True},
"extensions": {
"t_thresh": 0.5,
"spatial_refine_only": False,
"num_cond_frames": 0,
},
})
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,
)
print(f"Refinement complete! Video saved to: {refine_output_path}")
refine_generator.shutdown()
+79 -108
View File
@@ -41,37 +41,32 @@ def basic_generation():
print("LongCat T2V: Basic Generation (50 steps, 480p)")
print("=" * 60)
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-T2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {"model": {"longcat": {"enable_bsa": False}}},
})
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,
)
output_path = "outputs_video/longcat_t2v_basic"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"sampling": {
"height": 480,
"width": 832,
"num_frames": 93,
"num_inference_steps": 50,
"fps": 15,
"guidance_scale": 4.0,
"seed": SEED,
},
"output": {"output_path": output_path, "save_video": True},
})
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,
)
print(f"\nBasic generation complete! Video saved to: {output_path}")
generator.shutdown()
@@ -92,43 +87,34 @@ def distill_refine_generation():
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
print("-" * 40)
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-T2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {"longcat": {"enable_bsa": False}},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA",
"lora_nickname": "distilled",
},
},
})
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",
)
distill_output_path = "outputs_video/longcat_t2v_distill"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"sampling": {
"height": 480,
"width": 832,
"num_frames": 93,
"num_inference_steps": 16,
"fps": 15,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"output_path": distill_output_path, "save_video": True},
})
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,
)
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
generator.shutdown()
@@ -146,55 +132,40 @@ def distill_refine_generation():
print(f"Using stage 1 video: {distill_video_path}")
# Create a new generator with refinement LoRA and BSA enabled
refine_generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-T2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": True,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {
"longcat": {
"enable_bsa": True,
"bsa_sparsity": 0.875,
"bsa_chunk_q": [4, 4, 8],
"bsa_chunk_k": [4, 4, 8],
},
},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA",
"lora_nickname": "refinement",
},
},
})
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_output_path = "outputs_video/longcat_t2v_refine_720p"
refine_generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"refine_from": distill_video_path},
"sampling": {
"height": 720,
"width": 1280,
"num_inference_steps": 50,
"fps": 30,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"output_path": refine_output_path, "save_video": True},
"extensions": {
"t_thresh": 0.5,
"spatial_refine_only": False,
"num_cond_frames": 0,
},
})
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,
)
print(f"Refinement complete! Video saved to: {refine_output_path}")
refine_generator.shutdown()
+83 -112
View File
@@ -57,39 +57,34 @@ def basic_generation():
raise FileNotFoundError(f"Video not found at {VIDEO_PATH}. "
"Please provide a valid video path.")
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-VC-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": False, # set to True if GPU is out of memory
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {"model": {"longcat": {"enable_bsa": False}}},
})
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,
)
output_path = "outputs_video/longcat_vc_basic"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"video_path": VIDEO_PATH},
"sampling": {
"height": 480,
"width": 832,
"num_frames": 93,
"num_inference_steps": 50,
"fps": 15,
"guidance_scale": 4.0,
"seed": SEED,
},
"output": {"output_path": output_path, "save_video": True},
"extensions": {"num_cond_frames": NUM_COND_FRAMES},
})
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,
)
print(f"\nBasic generation complete! Video saved to: {output_path}")
generator.shutdown()
@@ -115,45 +110,36 @@ def distill_refine_generation():
print("\n[Stage 1] Distilled generation (16 steps, 480p)")
print("-" * 40)
generator = VideoGenerator.from_config({
"model_path": "FastVideo/LongCat-Video-VC-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": False,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {"longcat": {"enable_bsa": False}},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Distilled-LoRA",
"lora_nickname": "distilled",
},
},
})
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",
)
distill_output_path = "outputs_video/longcat_vc_distill"
generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"video_path": VIDEO_PATH},
"sampling": {
"height": 480,
"width": 832,
"num_frames": 93,
"num_inference_steps": 16,
"fps": 15,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"output_path": distill_output_path, "save_video": True},
"extensions": {"num_cond_frames": NUM_COND_FRAMES},
})
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,
)
print(f"Distilled generation complete! Video saved to: {distill_output_path}")
generator.shutdown()
@@ -172,55 +158,40 @@ 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_config({
"model_path": "FastVideo/LongCat-Video-T2V-Diffusers",
"engine": {
"num_gpus": 1,
"use_fsdp_inference": True,
"offload": {
"dit": True,
"vae": True,
"text_encoder": True,
"pin_cpu_memory": False,
},
},
"pipeline": {
"model": {
"longcat": {
"enable_bsa": True,
"bsa_sparsity": 0.875,
"bsa_chunk_q": [4, 4, 8],
"bsa_chunk_k": [4, 4, 8],
},
},
"components": {
"lora_path": "FastVideo/LongCat-Video-T2V-Refinement-LoRA",
"lora_nickname": "refinement",
},
},
})
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_output_path = "outputs_video/longcat_vc_refine_720p"
refine_generator.generate({
"prompt": PROMPT,
"negative_prompt": NEGATIVE_PROMPT,
"inputs": {"refine_from": distill_video_path},
"sampling": {
"height": 720,
"width": 1280,
"num_inference_steps": 50,
"fps": 30,
"guidance_scale": 1.0,
"seed": SEED,
},
"output": {"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
},
})
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,
)
print(f"Refinement complete! Video saved to: {refine_output_path}")
refine_generator.shutdown()
+9 -10
View File
@@ -17,19 +17,18 @@ def main() -> None:
# Uses FastVideo default sampling settings for LTX2 base.
generator = VideoGenerator.from_pretrained(
"Davids048/LTX2-Base-Diffusers",
{
"engine": {
"num_gpus": 1,
},
},
num_gpus=1,
)
output_path = "outputs_video/ltx2_basic/output_ltx2_base_t2v_1088_1920_1.1.mp4"
generator.generate({
"prompt": PROMPT,
"sampling": {"num_frames": 121, "height": 1088, "width": 1920},
"output": {"output_path": output_path, "save_video": True},
})
generator.generate_video(
prompt=PROMPT,
output_path=output_path,
save_video=True,
num_frames=121,
height=1088,
width=1920,
)
generator.shutdown()
@@ -49,7 +49,6 @@ from pathlib import Path
import torch._inductor.config as _inductor
from fastvideo import VideoGenerator
from fastvideo.api import GenerationResult
from fastvideo.configs.pipelines.base import PipelineConfig
from fastvideo.utils import maybe_download_model
@@ -81,9 +80,9 @@ PROMPT = os.getenv("LTX23_I2V_PROMPT", DEFAULT_PROMPT)
# Per-stage timing helpers --------------------------------------------------
def _print_stage_breakdown(result: GenerationResult, label: str) -> float | None:
def _print_stage_breakdown(result: dict, label: str) -> float | None:
"""Print stage execution times and return the sum, or None if missing."""
logging_info = result.logging_info
logging_info = result.get("logging_info")
stages = getattr(logging_info, "stages", None) if logging_info else None
if not stages:
print(f" [{label}] stage breakdown unavailable")
@@ -99,11 +98,11 @@ def _print_stage_breakdown(result: GenerationResult, label: str) -> float | None
def _collect_stage_times(
result: GenerationResult,
result: dict,
stage_times: dict[str, list[float]],
stage_order: OrderedDict[str, None],
) -> None:
logging_info = result.logging_info
logging_info = result.get("logging_info")
stages = getattr(logging_info, "stages", None) if logging_info else None
if not stages:
return
@@ -159,68 +158,52 @@ def main() -> None:
pipeline_config = PipelineConfig.from_pretrained(model_root)
pipeline_config.dit_config.quant_config = None
generator = VideoGenerator.from_config({
"model_path": model_root,
"engine": {
"num_gpus": 1,
"compile": {
"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,
**torch_compile_kwargs,
"vae_kwargs": torch_compile_kwargs,
},
# Keep everything resident — no CPU offload for serving-style runs.
"offload": {
"dit": False,
"text_encoder": False,
"vae": False,
},
},
"pipeline": {
# LTX-2.3 distilled uses the two-stage refine pipeline; the refine
# LoRA is intentionally empty for the distilled student.
"preset_overrides": {
"refine": {
"enabled": True,
"num_inference_steps": 3,
"guidance_scale": 1.0,
"add_noise": True,
},
},
"components": {"upsampler_weights": str(refine_upsampler_path)},
"model": {"ltx2": {"refine": {"lora_path": ""}}},
"experimental": {"pipeline_config": pipeline_config},
"vae_tiling": False,
},
})
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,
)
common_request = {
"prompt": PROMPT,
"negative_prompt": "", # distilled is CFG-free; no negative needed
"sampling": {
"guidance_scale": 1.0, # CFG=1 for distilled
"height": 1280,
"width": 832, # portrait runway aspect
"num_frames": 121,
"fps": 24, # ~5s clip
"num_inference_steps": 8, # distilled denoise steps
},
"output": {"save_video": True},
common_kwargs = dict(
prompt=PROMPT,
negative_prompt="", # distilled is CFG-free; no negative needed
guidance_scale=1.0, # CFG=1 for distilled
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.
"extensions": {
"ltx2_images": [(I2V_IMAGE, 0, 1.0)],
"ltx2_image_crf": 0.0,
},
}
ltx2_images=[(I2V_IMAGE, 0, 1.0)],
ltx2_image_crf=0.0,
save_video=True,
)
warmup_runs = 2
measured_runs = 2
@@ -235,11 +218,11 @@ 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({
**common_request,
"sampling": {**common_request["sampling"], "seed": 7},
"output": {**common_request["output"], "output_path": str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4")},
})
generator.generate_video(
output_path=str(OUTPUT_DIR / f"_warmup_{w + 1}.mp4"),
seed=7,
**common_kwargs,
)
dt = time.perf_counter() - t0
warmup_secs.append(dt)
print(f"[warmup {w + 1}/{warmup_runs}] wall={dt:.1f}s")
@@ -253,16 +236,16 @@ 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({
**common_request,
"sampling": {**common_request["sampling"], "seed": 2002 + m},
"output": {**common_request["output"], "output_path": str(out_path)},
})
result = generator.generate_video(
output_path=str(out_path),
seed=2002 + m,
**common_kwargs,
)
wall = time.perf_counter() - t0
e2e = (result.extra.get("e2e_latency") if isinstance(result, GenerationResult) else None) or wall
e2e = (result.get("e2e_latency") if isinstance(result, dict) 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, GenerationResult):
if isinstance(result, dict):
_print_stage_breakdown(result, f"measured {m + 1}")
_collect_stage_times(result, stage_times, stage_order)

Some files were not shown because too many files have changed in this diff Show More