[feat] [6/n] Improve API: add ltx2_two_stage preset

Land the ltx2_two_stage inference preset (PR 6 commit 1/5) that exposes
the public LTX-2 two-stage distilled flow: half-resolution denoise
followed by 2x spatial upsample + stage-2 refine denoise (3 steps with
the official distilled sigma schedule, or 2 steps with the reduced
variant).

Schema:
  - new _REFINE_STAGE PresetStageSpec with kind="refinement" and
    allowed per-request overrides {num_inference_steps, guidance_scale,
    image_crf, video_position_offset_sec}
  - new LTX2_TWO_STAGE preset with stage_schemas=(denoise, refine) and
    stage_defaults.refine={num_inference_steps: 2, guidance_scale: 1.0}
    matching the load_kwargs the internal ltx2-streaming gpu_pool uses
  - LTX2_TWO_STAGE registered via ALL_PRESETS -> _register_presets()

Init-time refine wiring (enabled, add_noise, upsampler/LoRA/transformer
paths) flows through generator.pipeline.preset_overrides.refine.* and
generator.pipeline.components.{upsampler_weights, lora_path} in a
follow-up commit — the refine stage's internal implementation
(fastvideo/pipelines/stages/ltx2_refine.py in FastVideo-internal) already
treats those as FastVideoArgs fields; this commit only ships the public
preset surface.

5 new tests under fastvideo/tests/api/test_presets.py::TestLtx2Presets:
registration count (3 presets now), two-stage topology, stage defaults,
valid per-request refine override keys, and unknown-override rejection.
This commit is contained in:
SolitaryThinker
2026-04-21 08:32:19 -07:00
parent 6b3c1223c6
commit 279e52ad8d
2 changed files with 83 additions and 2 deletions
+45 -1
View File
@@ -30,6 +30,18 @@ _DENOISE_STAGE = PresetStageSpec(
}),
)
_REFINE_STAGE = PresetStageSpec(
name="refine",
kind="refinement",
description="Latent-upsample + second-pass refine",
allowed_overrides=frozenset({
"num_inference_steps",
"guidance_scale",
"image_crf",
"video_position_offset_sec",
}),
)
LTX2_BASE = InferencePreset(
name="ltx2_base",
version=1,
@@ -77,4 +89,36 @@ LTX2_DISTILLED = InferencePreset(
},
)
ALL_PRESETS = (LTX2_BASE, LTX2_DISTILLED)
# Two-stage distilled flow: half-res denoise (stage 1) followed by
# spatial-upsample + second denoise (stage 2 "refine"). Stage-2
# sigmas are fixed in fastvideo.pipelines.stages.ltx2_refine — only
# num_inference_steps (2 or 3) and guidance_scale are user-tunable.
# Refine wiring (upsampler weights, optional LoRA, enable/add-noise)
# lives on generator.pipeline.preset_overrides.refine at init-time;
# per-request tuning goes through request.stage_overrides.refine.
LTX2_TWO_STAGE = InferencePreset(
name="ltx2_two_stage",
version=1,
model_family="ltx2",
description="LTX-2 distilled with 2x spatial refine (stage 1 half-res + stage 2 upsample+denoise)",
workload_type="t2v",
stage_schemas=(_DENOISE_STAGE, _REFINE_STAGE),
defaults={
"seed": 10,
"height": 1024,
"width": 1536,
"num_frames": 121,
"fps": 24,
"guidance_scale": 1.0,
"num_inference_steps": 8,
"negative_prompt": "",
},
stage_defaults={
"refine": {
"num_inference_steps": 2,
"guidance_scale": 1.0,
},
},
)
ALL_PRESETS = (LTX2_BASE, LTX2_DISTILLED, LTX2_TWO_STAGE)
+38 -1
View File
@@ -334,7 +334,7 @@ class TestLtx2Presets:
import fastvideo.registry # noqa: F401
presets = get_presets_for_family("ltx2")
names = {p.name for p in presets}
assert names == {"ltx2_base", "ltx2_distilled"}
assert names == {"ltx2_base", "ltx2_distilled", "ltx2_two_stage"}
def test_ltx2_base_lookup(self) -> None:
import fastvideo.registry # noqa: F401
@@ -349,6 +349,43 @@ class TestLtx2Presets:
assert p.defaults["num_inference_steps"] == 8
assert p.defaults["guidance_scale"] == 1.0
def test_ltx2_two_stage_is_two_stage(self) -> None:
import fastvideo.registry # noqa: F401
p = get_preset("ltx2_two_stage", "ltx2")
assert len(p.stage_schemas) == 2
assert p.stage_schemas[0].name == "denoise"
assert p.stage_schemas[1].name == "refine"
assert p.stage_schemas[1].kind == "refinement"
def test_ltx2_two_stage_stage_defaults(self) -> None:
import fastvideo.registry # noqa: F401
p = get_preset("ltx2_two_stage", "ltx2")
refine = p.stage_defaults["refine"]
# stage-2 refine only supports 2 or 3 denoising steps; preset
# defaults to 2 (matches gpu_pool.py load_kwargs).
assert refine["num_inference_steps"] == 2
assert refine["guidance_scale"] == 1.0
def test_ltx2_two_stage_refine_overrides_valid(self) -> None:
import fastvideo.registry # noqa: F401
p = get_preset("ltx2_two_stage", "ltx2")
validate_stage_overrides(
p, {"refine": {"num_inference_steps": 3}})
validate_stage_overrides(
p, {"refine": {"guidance_scale": 1.0}})
validate_stage_overrides(
p, {"refine": {"image_crf": 18}})
validate_stage_overrides(
p, {"refine": {"video_position_offset_sec": 2.5}})
def test_ltx2_two_stage_rejects_unknown_refine_override(self) -> None:
import fastvideo.registry # noqa: F401
from fastvideo.api.errors import ConfigValidationError
p = get_preset("ltx2_two_stage", "ltx2")
with pytest.raises(ConfigValidationError):
validate_stage_overrides(
p, {"refine": {"bogus_field": 1}})
# -------------------------------------------------------------------
# Hunyuan preset integration