Files
hao-ai-lab-FastVideo/v2/tests/test_nested_workflow.py
T
Will Lin 51898f48e9 [refactor] v2: rename models/ (recipe layer) -> recipes/; move toy backend -> platform/backends/toy.py
Frees v2/models/ to become the vendored-architecture namespace that mirrors fastvideo/models
(part of making v2 self-contained / able to replace fastvideo). The v2 recipe layer (per-family
card.py/loop.py/program.py + common.py + the build_*_engine re-exports) is the recipe, not the
architectures, so it moves to v2/recipes/. The pure-numpy toy/parity implementations (ToyDiT etc.)
move from v2/models/backend.py to v2/platform/backends/toy.py (alongside cpu.py/accel.py/torch_*).

Mechanical: all imports are absolute, so v2.models.<x> -> v2.recipes.<x> and v2.models.backend ->
v2.platform.backends.toy across v2/ + examples/ (89 files, 178 refs). No behavior change.
CPU mini green (202 passed, 2 skipped); all v2 files compile.
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.recipes 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"))