Files
hao-ai-lab-FastVideo/v2/tests/test_nested_workflow.py
T
SolitaryThinker 9489c6c1dd [feat] Five more stress tests: LTX-2 A/V, weight-sync, served reward, cache-dit, nested workflows
The remaining design_v3 probes (all except 8b speculative decoding). All fit with no new runtime
primitive (148 tests pass).

#6 LTX-2 joint audio+video (§9.11) — LTX-2 declared an audio_vae required_for t2vs but never used
   it; now a single 2-stage denoise carries a synchronized audio latent (conditioned on video),
   applies per-modality CFG (guidance_per_modality), and decodes via video VAE + audio VAE → video +
   audio. Gated on requesting audio, so the T2V path is byte-identical (existing tests untouched).
   ToyAudioVAE; build_ltx2_av_program. 5 tests.

#4 Live weight-sync under in-flight serving (§9.14) — WeightSyncController makes the freeze → drain →
   transfer → bump version + invalidate → resume lifecycle explicit. Tests: a mid-flight swap corrupts
   (the hazard); draining first leaves the in-flight request bit-identical to baseline while a
   post-sync request reflects new weights; transformer-only sync, so the frozen text-encoder cache
   survives. The RL flywheel's hardest correctness. 3 tests.

#5 Reward-model-as-a-served-card (§9.15) — a reward model is a card (scorer + a score loop emitting
   REWARD_BATCH units); ServedRewardScorer drop-in-replaces the numpy scorer so any RL method becomes
   RLHF/RLAIF with no method change. ToyRewardModel; models/reward/. 4 tests.

#7 Content-adaptive control flow (§9.12) — CacheDiTDenoiseLoop (isolated WanDenoiseLoop subclass)
   reuses the cached velocity when predictions barely change (cache-dit skip) and early-exits on
   convergence — variable step count; interleave parity holds across ragged loops. models/adaptive/. 4 tests.

#8a Nested workflows (§9.13) — a workflow stage can invoke another workflow (engine.run routes ids);
   requires/validate recurse; cycles caught at registration + a run-time guard (engine._wf_running).
   build_t2i_i2v_extend_workflow. 5 tests.

designv4 §9.11–§9.15 + falsifier/layout/closing updates (148 tests, 25 files). Also removed a
pre-existing unused import in ltx2/loop.py.
2026-07-04 17:15:34 +00:00

66 lines
3.0 KiB
Python

"""Nested workflows — recursive cross-model composition (design_v3 §13; §9.13).
A workflow id is a servable, so a `WorkflowStage` can invoke a *workflow* exactly as it invokes a
model. These tests build a workflow whose first stage IS the T2I→I2V workflow (then extends its video),
register it on an engine that already serves the inner workflow, and assert: it runs end-to-end;
`requires` lists the inner workflow; validation recurses; and a self-referencing workflow is rejected
by the cycle guard (no infinite recursion).
"""
from __future__ import annotations
import numpy as np
import pytest
from v2.models import build_image_video_engine, build_t2i_i2v_extend_workflow
from v2.program import Workflow, WorkflowStage
from v2.request import TaskType, make_request
from v2.runtime import Engine
def test_nested_workflow_runs_end_to_end():
eng = build_image_video_engine() # serves flux-t2i, wan-i2v, AND image_video.t2i_i2v
outer = build_t2i_i2v_extend_workflow()
eng.register_workflow(outer)
out = outer.run(eng, prompt="a red fox in snow", seed=2)
frames = np.asarray(out.artifacts["video"].frames)
assert frames.ndim == 4 and frames.shape[1] > 1 # the extended (I2V) video
def test_nested_workflow_requires_the_inner_workflow():
outer = build_t2i_i2v_extend_workflow()
assert outer.requires == ["image_video.t2i_i2v", "wan-i2v"] # a workflow id among its deps
# registering on an engine that does NOT serve the inner workflow fails fast
with pytest.raises(ValueError, match="requires unregistered"):
Engine().register_workflow(outer)
def test_nested_workflow_addressable_by_id():
eng = build_image_video_engine()
eng.register_workflow(build_t2i_i2v_extend_workflow())
assert eng.serves("image_video.t2i_i2v_extend")
out = eng.run(make_request(TaskType.T2V, "image_video.t2i_i2v_extend", "a lighthouse"))
assert out.artifacts["video"].frames is not None # engine.run routes the OUTER workflow,
# which routes the INNER workflow as its stage 1
def _selfref():
return Workflow("image_video.loop", [WorkflowStage(
"image_video.loop", lambda s: make_request(TaskType.T2V, "image_video.loop", s["prompt"]))])
def test_self_reference_caught_at_registration():
"""A workflow that requires itself can't even register — `requires` includes its own (unserved) id.
This is the primary cycle prevention (declared deps, validated)."""
eng = build_image_video_engine()
with pytest.raises(ValueError, match="requires unregistered"):
eng.register_workflow(_selfref())
def test_runtime_cycle_guard_is_defense_in_depth():
"""Even if a cycle slipped past validation (here forced by inserting directly), the run-time guard
raises rather than recursing forever."""
eng = build_image_video_engine()
eng._workflows["image_video.loop"] = _selfref() # bypass validate() to simulate a cycle
with pytest.raises(ValueError, match="cycle"):
eng.run(make_request(TaskType.T2V, "image_video.loop", "x"))