[refactor]: remove VideoGenerator.generate_video

VideoGenerator.generate(request) is the one inference entry point. The
generate_video keyword call, its mapping from flat keywords to a request
(legacy_generate_call_to_request and its helpers), and the tests of that
mapping are removed. Comments and docs that named generate_video describe
generate and request extensions instead.

scripts/ltx2_sr_alignment.py keeps its generate_video call, because its
reference run imports the separate FastVideo-internal package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012M91wnVFmPEvJ9h39r5BH7
This commit is contained in:
Davids048
2026-10-03 02:17:49 +00:00
co-authored by Claude Opus 5.5
parent d6ba5ced94
commit d728a0dc5f
21 changed files with 37 additions and 295 deletions
+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_video()` calls through FastVideo
loading, and `generate()` 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
+2 -2
View File
@@ -207,7 +207,7 @@
</Array>
</mxGeometry>
</mxCell>
<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">
<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">
<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_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">
<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">
<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

@@ -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_video, all green.
// /ws → BE → GPU pool → VideoGenerator.generate, all green.
const video = page.locator('video').first();
await expect(video).toHaveCount(1);
});
+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_video(...).logging_info`. Stage logs use pipeline-unique keys such as
`generate(...).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`,
@@ -192,9 +192,7 @@ def main() -> None:
last = _generate()
torch.cuda.synchronize()
wall = time.perf_counter() - t0
# generate_video returns a plain dict; attribute access would always
# fall back to wall time.
gen_t = last.get("generation_time") if isinstance(last, dict) else None
gen_t = last.generation_time
denoise_times.append(gen_t if gen_t is not None else wall)
print(f"[qad] {tag} run {i + 1}/{runs}: {wall:.2f}s wall "
f"(gen {denoise_times[-1]:.2f}s)")
@@ -47,8 +47,8 @@ def main() -> None:
def _run(tag: str) -> float:
save = tag == "measured"
# Modern typed-request API (generate_video is deprecated). Same
# prompt/seed/shapes both runs so the compiled graph is reused.
# Typed request API. Same prompt/seed/shapes both runs so the
# compiled graph is reused.
request: dict = {
"prompt": PROMPT,
"sampling": {
-77
View File
@@ -23,10 +23,6 @@ from fastvideo.api.schema import (
ContinuationState,
GenerationRequest,
GeneratorConfig,
InputConfig,
OutputConfig,
RequestRuntimeConfig,
SamplingConfig,
)
from fastvideo.api.sampling_param import SamplingParam
from fastvideo.fastvideo_args import FastVideoArgs
@@ -34,16 +30,8 @@ from fastvideo.pipelines.basic.ltx2.stage_overrides import (
refine_preset_override_fields,
refine_stage_override_fields,
)
from fastvideo.utils import shallow_asdict
_INPUT_FIELD_NAMES = {field.name for field in fields(InputConfig)}
_SAMPLING_FIELD_NAMES = {field.name for field in fields(SamplingConfig)}
_RUNTIME_FIELD_NAMES = {field.name for field in fields(RequestRuntimeConfig)}
_OUTPUT_FIELD_NAMES = {field.name for field in fields(OutputConfig)}
_MISSING = object()
_LEGACY_REQUEST_ALIASES = {
"neg_prompt": "negative_prompt",
}
REQUEST_BATCH_EXTRA_PASSTHROUGH_FIELDS = (
"ltx2_audio_latents",
"ltx2_audio_clean_latent",
@@ -198,34 +186,6 @@ def normalize_generation_request(request: GenerationRequest | Mapping[str, Any],
return normalized
def legacy_generate_call_to_request(
prompt: str | None,
sampling_param: SamplingParam | None,
*,
mouse_cond: Any | None = None,
keyboard_cond: Any | None = None,
grid_sizes: Any | None = None,
legacy_kwargs: Mapping[str, Any] | None = None,
) -> GenerationRequest:
raw = _sampling_param_to_request_raw(sampling_param)
if prompt is not None:
raw["prompt"] = prompt
for key, value in (legacy_kwargs or {}).items():
_apply_request_field(raw, key, value)
if mouse_cond is not None:
raw.setdefault("inputs", {})["mouse_cond"] = mouse_cond
if keyboard_cond is not None:
raw.setdefault("inputs", {})["keyboard_cond"] = keyboard_cond
if grid_sizes is not None:
raw.setdefault("inputs", {})["grid_sizes"] = grid_sizes
normalized = parse_config(GenerationRequest, raw)
bind_generation_request_raw(normalized, raw)
return normalized
def request_to_sampling_param(
request: GenerationRequest,
*,
@@ -354,42 +314,6 @@ def _compile_config_to_torch_kwargs(compile_config: CompileConfig, ) -> dict[str
return out
def _sampling_param_to_request_raw(sampling_param: SamplingParam | None, ) -> dict[str, Any]:
if sampling_param is None:
return {}
raw: dict[str, Any] = {}
for key, value in shallow_asdict(sampling_param).items():
if key == "prompt":
continue
_apply_request_field(raw, key, deepcopy(value))
return raw
def _apply_request_field(
raw: dict[str, Any],
key: str,
value: Any,
) -> None:
key = _LEGACY_REQUEST_ALIASES.get(key, key)
if key == "negative_prompt":
raw["negative_prompt"] = value
return
if key in _INPUT_FIELD_NAMES:
raw.setdefault("inputs", {})[key] = value
return
if key in _SAMPLING_FIELD_NAMES:
raw.setdefault("sampling", {})[key] = value
return
if key in _RUNTIME_FIELD_NAMES:
raw.setdefault("runtime", {})[key] = value
return
if key in _OUTPUT_FIELD_NAMES:
raw.setdefault("output", {})[key] = value
return
raw.setdefault("extensions", {})[key] = value
def request_to_batch_extra(request: GenerationRequest) -> dict[str, Any]:
"""Extract typed-request extensions consumed through ``ForwardBatch.extra``."""
return {
@@ -587,7 +511,6 @@ __all__ = [
"explicit_request_updates",
"generator_config_to_fastvideo_args",
"legacy_from_pretrained_to_config",
"legacy_generate_call_to_request",
"load_generator_config_from_file",
"normalize_generation_request",
"normalize_generator_config",
+1 -1
View File
@@ -140,7 +140,7 @@ class SamplingParam:
ltx2_stg_blocks_audio: list[int] = field(default_factory=lambda: [29])
# LTX-2 image / video / continuation conditioning. These flow from
# generate_video(...) kwargs through ``sampling_param.update(kwargs)``
# request ``extensions`` through ``sampling_param.update(...)``
# onto the ForwardBatch fields of the same name. ``ltx2_image_crf``
# gates the conditioning-image H.264 re-encode; the streaming
# session controller passes ``ltx2_image_crf=0.0`` because it
@@ -19,7 +19,7 @@ backend differs from the Wan-tuned ``video_sparse_attn``:
- Non-video *queries* are always dense. Non-video *keys* are either
always-selected for every query ("exempt", default) or compete in
top-k under a FLOP-matched budget ("compete") — the ablation axis,
switched per request via ``generate_video(..., vsa_mode=...)``
switched per request via the request extension ``vsa_mode``
(default: exempt). Per-request scheduling knobs
(``vsa_dense_first_n_steps``, ``vsa_dense_layers``) let mixed schedules
run the diffuse steps/layers dense while pushing the rest harder.
+1 -76
View File
@@ -32,7 +32,6 @@ from fastvideo.api.compat import (
expand_request_prompt_batch,
generator_config_to_fastvideo_args,
legacy_from_pretrained_to_config,
legacy_generate_call_to_request,
load_generator_config_from_file,
normalize_generation_request,
normalize_generator_config,
@@ -401,80 +400,6 @@ class VideoGenerator:
output=OutputConfig(save_video=False, return_frames=False),
)
def generate_video(
self,
prompt: str | None = None,
sampling_param: SamplingParam | None = None,
# Action control inputs (Matrix-Game)
mouse_cond: torch.Tensor | None = None,
keyboard_cond: torch.Tensor | None = None,
grid_sizes: tuple[int, int, int] | list[int] | torch.Tensor
| None = None,
**kwargs,
) -> dict[str, Any] | list[dict[str, Any]]:
"""
Generate a video based on the given prompt.
Args:
prompt: The prompt to use for generation (optional if prompt_txt is provided)
negative_prompt: The negative prompt to use (overrides the one in fastvideo_args)
output_path: Path to save the video (overrides the one in fastvideo_args)
prompt_path: Path to prompt file
save_video: Whether to save the video to disk
return_frames: Whether to include raw frames in the result dict
num_inference_steps: Number of denoising steps (overrides fastvideo_args)
guidance_scale: Classifier-free guidance scale (overrides fastvideo_args)
num_frames: Number of frames to generate (overrides fastvideo_args)
height: Height of generated video (overrides fastvideo_args)
width: Width of generated video (overrides fastvideo_args)
fps: Frames per second for saved video (overrides fastvideo_args)
seed: Random seed for generation (overrides fastvideo_args)
callback: Callback function called after each step
callback_steps: Number of steps between each callback
Returns:
A metadata dictionary for single-prompt generation, or a list of
metadata dictionaries for prompt-file batch generation.
"""
log_queue = kwargs.pop("log_queue", None)
warnings.warn(
"VideoGenerator.generate_video(...) is deprecated; use "
"VideoGenerator.generate(request=...) instead.",
DeprecationWarning,
stacklevel=2,
)
if log_queue:
self.executor.set_log_queue(log_queue)
try:
extra_overrides: dict[str, Any] = {}
for _ek in _BATCH_EXTRA_PASSTHROUGH_KEYS:
if _ek in kwargs:
extra_overrides[_ek] = kwargs.pop(_ek)
request = legacy_generate_call_to_request(
prompt,
sampling_param,
mouse_cond=mouse_cond,
keyboard_cond=keyboard_cond,
grid_sizes=grid_sizes,
legacy_kwargs=kwargs,
)
resolved_sampling_param = request_to_sampling_param(
request,
model_path=self.fastvideo_args.model_path,
)
return self._generate_video_impl(
prompt=request.prompt,
sampling_param=resolved_sampling_param,
fastvideo_args=self.fastvideo_args,
**extra_overrides,
)
finally:
if log_queue:
self.executor.clear_log_queue()
def _generate_request_impl(
self,
request: GenerationRequest,
@@ -530,7 +455,7 @@ class VideoGenerator:
fastvideo_args: FastVideoArgs | None = None,
**kwargs,
) -> dict[str, Any] | list[np.ndarray] | list[dict[str, Any]]:
"""Internal implementation of generate_video."""
"""Run one prompt, or each prompt of a prompt file, through the pipeline with ``sampling_param``."""
if fastvideo_args is None:
fastvideo_args = self.fastvideo_args
+1 -2
View File
@@ -3,7 +3,7 @@
A :class:`PromptDataset` is an iterable of *sample dicts* describing the
prompts and conditions for a benchmark. Each sample is a plain dict —
no dataclass, no schema enforcement — that flows directly into both
generation (``VideoGenerator.generate_video(**sample)``) and scoring
generation (a ``VideoGenerator.generate`` request) and scoring
(``Evaluator.evaluate(**eval_kwargs)``). The runner picks well-known
keys (``prompt``, ``n_samples``, ``dimensions``, ``auxiliary_info``,
...) and passes the rest through.
@@ -11,7 +11,6 @@ keys (``prompt``, ``n_samples``, ``dimensions``, ``auxiliary_info``,
This matches the surrounding FastVideo style:
* :class:`fastvideo.dataset.validation_dataset.ValidationDataset` yields dicts.
* :meth:`fastvideo.VideoGenerator.generate_video` consumes ``**kwargs``.
* :meth:`fastvideo.eval.Evaluator.evaluate` consumes ``**kwargs``.
To add a new benchmark:
@@ -154,7 +154,7 @@ class MiniMaxH3DenoisingStage(PipelineStage):
if vsa_metadata_builder is not None:
vsa_patch_size = fastvideo_args.pipeline_config.dit_config.patch_size
vsa_prefix_segments = _h3_vsa_prefix_segments(layout, vsa_patch_size)
# Per-request knobs (sweeps flip these between generate_video calls
# Per-request knobs (sweeps flip these between generate calls
# without respawning workers); mode None defers to the env default.
vsa_mode = batch.extra.get("vsa_mode", "exempt")
if vsa_mode not in ("exempt", "compete"):
@@ -62,7 +62,7 @@ def _disable_tf32_for_stable_audio() -> None:
class StableAudioPipeline(ComposedPipelineBase):
"""Stable Audio Open 1.0 pipeline.
Mode is kwargs-driven on `generate_video()`:
Mode is driven by the request `extensions` passed to `generate()`:
* Text-to-audio (default) -- `prompt=...`, `audio_end_in_s=...`
* Audio-to-audio variation -- add `init_audio=ref` (and optionally
@@ -63,7 +63,7 @@ class StableAudioDecodingStage(PipelineStage):
# Raw tensor for parity tests.
batch.extra["decoded_audio"] = decoded.detach().cpu()
# `VideoGenerator.generate_video` is video-shaped (asserts
# `VideoGenerator.generate` is video-shaped (asserts
# `output_batch.output is not None`); fill with a placeholder of
# the expected `[B, 3, num_frames, H, W]` shape — the real audio
# is on `batch.extra` above. Keep this compatibility shim until
@@ -13,8 +13,8 @@ invocation closed stdin before the writer thread had pushed every
frame, surfacing as ``BrokenPipeError`` in the streaming server.
These tests pin two contracts:
1. ``_BATCH_EXTRA_PASSTHROUGH_KEYS`` lists the exact set of kwargs
pulled out of ``generate_video(**kwargs)`` for ``batch.extra``.
1. ``_BATCH_EXTRA_PASSTHROUGH_KEYS`` lists the exact set of request
extensions that go to ``batch.extra``.
2. ``SamplingParam.update()`` raises ``ValueError`` on unknown keys.
"""
from __future__ import annotations
@@ -13,10 +13,7 @@ This test makes sure:
1. The public typed API can represent everything Dreamverse currently
passes at init time (``legacy_from_pretrained_to_config``).
2. The request-path Dreamverse uses (``generator.generate_video(**kwargs)``
with per-segment flags) round-trips through the typed ``GenerationRequest``
without reintroducing private-only fields at the public boundary.
3. Private-only Dreamverse fields that don't belong on the public
2. Private-only Dreamverse fields that don't belong on the public
surface either go to ``pipeline.experimental`` / ``request.extensions``
(the documented escape hatch) or raise explicitly, rather than
silently becoming part of the public compatibility promise.
@@ -36,7 +33,6 @@ from fastvideo.api import (
)
from fastvideo.api.compat import (
legacy_from_pretrained_to_config,
legacy_generate_call_to_request,
normalize_generation_request,
)
@@ -151,35 +147,6 @@ class TestDreamverseRequestShape:
"""The per-segment Dreamverse request path mirrors OpenAI's shape
plus a few LTX-2 knobs. All of them must have a typed home."""
def test_basic_request_fields_round_trip(self):
# The request path calls legacy_generate_call_to_request with a
# prompt + legacy kwargs; verify the typed shape carries them.
legacy_kwargs = {
"num_frames": 121,
"height": 1024,
"width": 1536,
"num_inference_steps": 8,
"guidance_scale": 1.0,
"seed": 42,
"fps": 24,
"negative_prompt": "blurry",
}
request = legacy_generate_call_to_request(
prompt="a fox running",
sampling_param=None,
legacy_kwargs=legacy_kwargs,
)
request = normalize_generation_request(request)
assert request.prompt == "a fox running"
assert request.negative_prompt == "blurry"
assert request.sampling.num_frames == 121
assert request.sampling.height == 1024
assert request.sampling.width == 1536
assert request.sampling.num_inference_steps == 8
assert request.sampling.guidance_scale == 1.0
assert request.sampling.seed == 42
assert request.sampling.fps == 24
def test_return_state_reaches_output_config(self):
"""PR 7 added ``output.return_state`` — must survive the legacy
translation path so Dreamverse callers can opt in."""
@@ -897,79 +897,6 @@ def test_generate_removes_deleted_loaded_stage_overrides(monkeypatch, tmp_path):
assert captured["sampling_param"].t_thresh == 0.5
def test_generate_video_legacy_call_uses_legacy_impl(monkeypatch):
generator = _new_runtime_video_generator()
captured = {}
def fake_generate_video_impl(
prompt=None,
sampling_param=None,
mouse_cond=None,
keyboard_cond=None,
grid_sizes=None,
**kwargs,
):
captured["prompt"] = prompt
captured["sampling_param"] = sampling_param
captured["mouse_cond"] = mouse_cond
captured["keyboard_cond"] = keyboard_cond
captured["grid_sizes"] = grid_sizes
captured["kwargs"] = kwargs
return {"prompts": prompt, "video_path": "outputs/test.mp4"}
monkeypatch.setattr(generator, "_generate_video_impl", fake_generate_video_impl)
with pytest.warns(DeprecationWarning):
result = generator.generate_video(
prompt="legacy prompt",
num_frames=49,
output_path="outputs/legacy",
save_video=False,
log_queue="queue-token",
)
assert captured["prompt"] == "legacy prompt"
assert captured["sampling_param"].num_frames == 49
assert captured["sampling_param"].output_path == "outputs/legacy"
assert captured["sampling_param"].save_video is False
assert result["video_path"] == "outputs/test.mp4"
def test_generate_video_legacy_call_routes_compat_kwargs(monkeypatch):
generator = _new_runtime_video_generator()
generator.fastvideo_args.pipeline_config = SimpleNamespace(embedded_cfg_scale=1.0)
captured = {}
def fake_generate_video_impl(
prompt=None,
sampling_param=None,
mouse_cond=None,
keyboard_cond=None,
grid_sizes=None,
**kwargs,
):
captured["prompt"] = prompt
captured["sampling_param"] = sampling_param
captured["kwargs"] = kwargs
return {"prompts": prompt, "video_path": "outputs/test.mp4"}
monkeypatch.setattr(generator, "_generate_video_impl", fake_generate_video_impl)
with pytest.warns(DeprecationWarning):
result = generator.generate_video(
prompt="legacy prompt",
neg_prompt="custom negative",
embedded_cfg_scale=7.5,
)
assert captured["prompt"] == "legacy prompt"
assert captured["sampling_param"].negative_prompt == "custom negative"
assert captured["sampling_param"].embedded_cfg_scale == 7.5
assert captured["kwargs"]["fastvideo_args"] is generator.fastvideo_args
assert generator.fastvideo_args.pipeline_config.embedded_cfg_scale == 1.0
assert result["video_path"] == "outputs/test.mp4"
def test_generate_batch_prompt_file_returns_typed_results(tmp_path, monkeypatch):
generator = _new_runtime_video_generator()
_patch_sampling_param_from_pretrained(monkeypatch)
+2 -2
View File
@@ -110,12 +110,12 @@ class Executor(ABC):
@abstractmethod
def set_log_queue(self, log_queue: Queue | None) -> None:
"""Forward worker logs to the given queue. Call before generate_video."""
"""Forward worker logs to the given queue. Call before generate."""
self.collective_rpc("set_log_queue", kwargs={"log_queue": log_queue})
@abstractmethod
def clear_log_queue(self) -> None:
"""Stop forwarding worker logs to the queue. Call after generate_video."""
"""Stop forwarding worker logs to the queue. Call after generate."""
self.collective_rpc("clear_log_queue")
@abstractmethod
+2 -2
View File
@@ -283,11 +283,11 @@ class MultiprocExecutor(Executor):
raise RuntimeError(f"Worker {i} failed to merge LoRA weights")
def set_log_queue(self, log_queue: Queue | None) -> None:
"""Forward worker logs to the given queue. Call before generate_video."""
"""Forward worker logs to the given queue. Call before generate."""
self.collective_rpc("set_log_queue", kwargs={"log_queue": log_queue})
def clear_log_queue(self) -> None:
"""Stop forwarding worker logs to the queue. Call after generate_video."""
"""Stop forwarding worker logs to the queue. Call after generate."""
self.collective_rpc("clear_log_queue")
def collective_rpc(self,
@@ -682,20 +682,23 @@ def main():
print(" 1. Test basic generation:")
print(" from fastvideo import VideoGenerator")
print(f" generator = VideoGenerator.from_pretrained('{output_dir}')")
print(" video = generator.generate_video(")
print(" prompt='A cat playing piano',")
print(" num_inference_steps=50")
print(" )")
print(" video = generator.generate({")
print(" 'prompt': 'A cat playing piano',")
print(" 'sampling': {'num_inference_steps': 50},")
print(" })")
print()
if (output_dir / "lora" / "distilled").exists():
print(" 2. Test distilled generation (16 steps with LoRA):")
print(f" generator = VideoGenerator.from_pretrained('{output_dir}',")
print(f" lora_path='{output_dir}/lora/distilled',")
print(" lora_nickname='distilled')")
print(" video = generator.generate_video(")
print(" prompt='A cat playing piano',")
print(" num_inference_steps=16,")
print(" guidance_scale=1.0)")
print(" generator = VideoGenerator.from_config({")
print(f" 'model_path': '{output_dir}',")
print(" 'pipeline': {'components': {")
print(f" 'lora_path': '{output_dir}/lora/distilled',")
print(" 'lora_nickname': 'distilled'}},")
print(" })")
print(" video = generator.generate({")
print(" 'prompt': 'A cat playing piano',")
print(" 'sampling': {'num_inference_steps': 16, 'guidance_scale': 1.0},")
print(" })")
print()
print()