The v2 top level had grown to ~27 dirs + 13 loose files — half of them
single-concept abstraction shells (memory/ 86 LOC, transport/ 222, parity/ 178,
extend/ 226) and half vendored fastvideo code sitting as peers to the actual v2
design. Regroup to mirror README section 3 "Planes & dependency order":
core/ enums+types, card, loop, program, parity, request, parallel
(the model-native contracts; no kernels)
runtime/ + folded-in substrate: cache, memory, transport, extend
(the import graph shows only runtime consumes them; compile/cudagraph
already lived here)
serving/ + deploy/ (products / fleet)
_vendor/ all copied fastvideo: models, layers, attention, configs, distributed,
platforms, api, hooks, logging_utils, third_party + fastvideo_args/
utils/logger/envs/forward_context — internal layout unchanged, still
mirrors upstream for diffing
28 dirs + 13 root files -> 8 dirs + 6 root files. Pure mechanical move: 947
absolute-import paths rewritten (v2.X -> v2.{core,runtime,serving,_vendor}.X),
boundary-anchored so platform/ (native dispatch) and platforms/ (vendored CUDA
detect) no longer collide and neither does hooks/ vs extend/. Deleted the empty
v2/loader/. README section 3 + 16 updated; stale test count corrected
(34 files/216 tests -> 22 files/143 tests).
Validated: `import v2` stays torch-free; `v2/run_tests.py` and `pytest v2/tests/`
-> 143 passed, 2 skipped on the numpy toy backend. (On a GPU box force the toy
backend with CUDA_VISIBLE_DEVICES="" or detect() picks cuda.) No external importer
changed — examples use the re-exported `from v2 import VideoGenerator`.
79 lines
3.3 KiB
Python
79 lines
3.3 KiB
Python
"""Inference paths: Wan2.1 T2V, LTX2.3 two-stage distilled, Wan-causal streaming."""
|
||
from __future__ import annotations
|
||
|
||
import numpy as np
|
||
|
||
from v2.recipes import build_default_engine
|
||
from v2.recipes.ltx2 import BASE_SIGMAS, REFINE_SIGMAS, build_ltx2_card
|
||
from v2.recipes.wan21 import build_wan21_card
|
||
from v2.recipes.wan_causal import build_wan_causal_card
|
||
from v2.core.request import DiffusionParams, OutputSpec, TaskType, make_request
|
||
|
||
|
||
def _eng():
|
||
return build_default_engine()
|
||
|
||
|
||
def test_wan_t2v_produces_video_and_latents():
|
||
eng = _eng()
|
||
req = make_request(TaskType.T2V, "wan2.1-1.3b", "a cat",
|
||
diffusion=DiffusionParams(num_steps=4, seed=1))
|
||
out = eng.run(req)
|
||
assert out.artifacts["video"].frames is not None
|
||
assert out.artifacts["latents"].latent is not None
|
||
assert out.metrics["denoise_steps"] == 4.0
|
||
|
||
|
||
def test_ltx2_two_stage_step_counts():
|
||
eng = _eng()
|
||
out = eng.run(make_request(TaskType.T2V, "ltx2-2stage-distilled", "a sunset",
|
||
diffusion=DiffusionParams(seed=2)))
|
||
# 8-step base + 3-step refine = the distilled schedule
|
||
assert out.metrics["base_steps"] == float(len(BASE_SIGMAS) - 1) == 8.0
|
||
assert out.metrics["refine_steps"] == float(len(REFINE_SIGMAS) - 1) == 3.0
|
||
assert out.artifacts["video"].frames is not None
|
||
|
||
|
||
def test_causal_chunk_rollout_frame_count():
|
||
eng = _eng()
|
||
out = eng.run(make_request(TaskType.T2V, "wan-causal-sf-1.3b", "a river",
|
||
diffusion=DiffusionParams(seed=3)))
|
||
assert out.metrics["chunks"] == 7.0
|
||
# 7 chunks × 3 latent frames/chunk (the self-forcing native block) over the temporal axis -> 81 video
|
||
assert out.artifacts["latents"].latent.shape[1] == 21
|
||
|
||
|
||
def test_determinism_same_seed_bit_identical():
|
||
eng = _eng()
|
||
r = lambda: make_request(TaskType.T2V, "wan2.1-1.3b", "x", diffusion=DiffusionParams(num_steps=4, seed=5))
|
||
a = eng.run(r()).artifacts["latents"].latent
|
||
b = eng.run(r()).artifacts["latents"].latent
|
||
assert np.array_equal(a, b)
|
||
|
||
|
||
def test_different_seed_differs():
|
||
eng = _eng()
|
||
a = eng.run(make_request(TaskType.T2V, "wan2.1-1.3b", "x",
|
||
diffusion=DiffusionParams(num_steps=4, seed=1))).artifacts["latents"].latent
|
||
b = eng.run(make_request(TaskType.T2V, "wan2.1-1.3b", "x",
|
||
diffusion=DiffusionParams(num_steps=4, seed=2))).artifacts["latents"].latent
|
||
assert not np.array_equal(a, b)
|
||
|
||
|
||
def test_streaming_emits_chunks_only_when_requested():
|
||
eng = _eng()
|
||
off = eng.run(make_request(TaskType.T2V, "wan2.1-1.3b", "x",
|
||
diffusion=DiffusionParams(num_steps=4, seed=1)))
|
||
on = eng.run(make_request(TaskType.T2V, "wan2.1-1.3b", "x",
|
||
diffusion=DiffusionParams(num_steps=4, seed=1),
|
||
outputs=OutputSpec(stream={"video": True})))
|
||
assert off.metrics.get("stream_chunks", 0) == 0
|
||
assert on.metrics.get("stream_chunks", 0) == 4 # one preview per denoise step
|
||
|
||
|
||
def test_all_cards_recipe_runtime_bound():
|
||
# the (recipe, runtime) pair: every card's recipe assumes a loop the card declares
|
||
for build in (build_wan21_card, build_ltx2_card, build_wan_causal_card):
|
||
card = build()
|
||
assert card.recipe.assumes_loop in card.loops
|