Files
hao-ai-lab-FastVideo/v2/tests/test_t2i_i2v_workflow.py
SolitaryThinker db866dfb04 [misc] v2: simplify docstrings/comments + drop deleted-design-doc citations
Sweep all 296 v2 modules: simplify verbose docstrings/comments and remove 376 dangling
"(design_vN §X)" citations to the now-deleted design docs (v2/README.md is the source of
truth). Comment/docstring-only — AST-verified code-identical; the CPU suite holds at 237
passed / 5 pre-existing. Also applies yapf + ruff --fix auto-fixes (import ordering,
forward-ref annotation de-quoting under `from __future__ import annotations`; behavior-
neutral, suite-confirmed) and adds the legitimate domain terms mot/clen/te to the codespell
ignore-list. Remaining ruff (24) + mypy (68 no-untyped-call) findings are pre-existing v2
debt, untouched here.
2026-06-19 20:54:59 +00:00

147 lines
6.8 KiB
Python

"""Cross-model T2I→I2V workflow — composition across model *instances*.
LTX-2 already proves same-card multi-stage diffusion (base→refine). This proves the harder case the
single-instance ``Program`` runner cannot express: chaining two *different* models (a T2I model and an
I2V model — the realistic FLUX→Wan pipeline) via a ``Workflow``, threading the image artifact between
them, with the I2V stage genuinely conditioned on the generated image. The per-model interleave-parity
guarantee is preserved because cross-model is a Workflow boundary, not a program loop step.
"""
from __future__ import annotations
import numpy as np
import pytest
from v2.recipes import build_image_video_engine, build_t2i_then_i2v_workflow, register_workflows
from v2.parity import assert_interleave_parity
from v2.program import Workflow, WorkflowRegistry, WorkflowStage
from v2.request import DiffusionParams, TaskType, make_request
from v2.request.modalpart import ImagePart, TextPart
from v2.runtime import Engine
def _i2v(eng, image, *, prompt="a scene", seed=9):
req = make_request(TaskType.I2V, "wan-i2v", prompt,
inputs=(TextPart(prompt), ImagePart(pixels=image)),
diffusion=DiffusionParams(num_steps=4, num_frames=81, seed=seed))
return eng.run(req).artifacts["video"].frames
def _t2i(eng, prompt, seed):
req = make_request(TaskType.T2I, "flux-t2i", prompt,
diffusion=DiffusionParams(num_steps=4, num_frames=1, seed=seed))
return eng.run(req).artifacts["image"].tensor
# --- the workflow ----------------------------------------------------------------- #
def test_t2i_then_i2v_workflow_produces_video():
eng = build_image_video_engine()
out = build_t2i_then_i2v_workflow().run(eng, prompt="a red fox in snow", seed=3)
frames = np.asarray(out.artifacts["video"].frames)
assert frames.ndim == 4 and frames.shape[1] > 1 # a multi-frame video, not a still
def test_workflow_crosses_two_distinct_instances():
eng = build_image_video_engine()
a = eng._registry["flux-t2i"][0]
b = eng._registry["wan-i2v"][0]
assert a is not b
assert a.card.model_id != b.card.model_id # two separate models / weight sets
assert a.component("transformer") is not b.component("transformer")
def test_i2v_stage_consumes_the_image():
"""The hand-off is real: swapping the conditioning image changes the video; the same image is
deterministic."""
eng = build_image_video_engine()
img1 = _t2i(eng, "alpha", seed=1)
img2 = _t2i(eng, "totally different scene", seed=2)
assert not np.array_equal(np.asarray(img1), np.asarray(img2))
v1 = _i2v(eng, img1)
v2 = _i2v(eng, img2)
assert not np.array_equal(np.asarray(v1), np.asarray(v2)) # image changes the video
assert np.array_equal(np.asarray(_i2v(eng, img1)), np.asarray(v1)) # same image ⇒ identical
def test_workflow_propagates_prompt_to_both_stages():
eng = build_image_video_engine()
a = build_t2i_then_i2v_workflow().run(eng, prompt="alpha", seed=1).artifacts["video"].frames
b = build_t2i_then_i2v_workflow().run(eng, prompt="beta gamma delta", seed=1).artifacts["video"].frames
assert not np.array_equal(np.asarray(a), np.asarray(b))
# --- the two cards are independently usable + each preserves its parity gate ------- #
def test_each_stage_runs_standalone():
eng = build_image_video_engine()
img = _t2i(eng, "a lighthouse", seed=5)
assert np.asarray(img).shape[1] == 1 # T2I ⇒ single frame (an image)
vid = _i2v(eng, img)
assert np.asarray(vid).shape[1] > 1 # I2V ⇒ video
def test_each_model_still_passes_interleave_parity():
eng = build_image_video_engine()
t2i = [make_request(TaskType.T2I, "flux-t2i", p,
diffusion=DiffusionParams(num_steps=4, num_frames=1, seed=s))
for p, s in [("a", 1), ("b", 2), ("a", 1)]]
assert not assert_interleave_parity(eng, t2i)
def test_workflow_rejects_model_mismatch():
"""A stage that builds a request for the wrong model is a programming error, caught early."""
eng = build_image_video_engine()
bad = Workflow("bad", [WorkflowStage(
"flux-t2i",
lambda s: make_request(TaskType.I2V, "wan-i2v", "x"))]) # declares flux-t2i, builds wan-i2v
with pytest.raises(ValueError):
bad.run(eng, prompt="x")
# --- naming + registration: a workflow is a first-class servable ------------------ #
def test_workflow_is_addressable_by_its_id_like_a_model():
"""The workflow_id is a servable name in the SAME namespace as model ids: serves() reports it and
engine.run routes to it — so the OpenAI server / fleet address it exactly like a card."""
eng = build_image_video_engine()
assert eng.serves("image_video.t2i_i2v") # registered as a servable
assert eng.serves("flux-t2i") and eng.serves("wan-i2v") # its required cards too
out = eng.run(make_request(TaskType.T2V, "image_video.t2i_i2v", "a fox",
diffusion=DiffusionParams(seed=3)))
assert np.asarray(out.artifacts["video"].frames).shape[1] > 1
def test_workflow_declares_and_validates_required_cards():
"""``requires`` is declared (the stage model_ids); registration fails fast if a card is absent."""
wf = build_t2i_then_i2v_workflow()
assert wf.requires == ["flux-t2i", "wan-i2v"]
with pytest.raises(ValueError, match="requires unregistered"):
Engine().register_workflow(wf) # no cards registered → fail fast
def test_workflow_id_must_not_collide_with_a_model_id():
eng = build_image_video_engine()
clash = Workflow("flux-t2i", [WorkflowStage("flux-t2i", lambda s: None)]) # id == a card id
with pytest.raises(ValueError, match="collides"):
eng.register_workflow(clash)
def test_workflow_registry_catalog():
reg = WorkflowRegistry()
reg.register("image_video.t2i_i2v", build_t2i_then_i2v_workflow)
assert "image_video.t2i_i2v" in reg and reg.names() == ["image_video.t2i_i2v"]
assert reg.build("image_video.t2i_i2v").requires == ["flux-t2i", "wan-i2v"]
with pytest.raises(ValueError, match="already registered"):
reg.register("image_video.t2i_i2v", build_t2i_then_i2v_workflow)
with pytest.raises(KeyError):
reg.build("nonexistent.workflow")
def test_register_workflows_helper_brings_up_cards_and_workflow():
"""The declarative catalog: one call registers the workflow and the cards it needs."""
eng = register_workflows(Engine(), only=["image_video.t2i_i2v"])
assert eng.serves("image_video.t2i_i2v")
out = eng.run(make_request(TaskType.T2V, "image_video.t2i_i2v", "a city",
diffusion=DiffusionParams(seed=1)))
assert out.artifacts["video"].frames is not None