Files
hao-ai-lab-FastVideo/v2/models/wan21/card.py
T
SolitaryThinker 4e331eb7ff [refactor] v2: use absolute imports (v2.*) everywhere instead of relative
Mechanical conversion of every relative import under v2/ to an absolute v2.* path
(from .x / ..x / ...x -> from v2.<pkg>.x) so imports are unambiguous, grep-able, and
stable when code is copied/moved between entrypoints (VideoGenerator, CLI, server).

Surgical prefix-only rewrite: only the 'from <dots><module>' prefix changed — import
names, parentheses, multi-line formatting, comments, and ordering are byte-for-byte
preserved (no collapsing, no reorder, no unrelated reformatting).

- 471 imports across 125 files; v2/tests/ was already absolute (untouched).
- Validated: all 125 files compile, every 'from v2.* import' target resolves to a real
  module/package, zero relative imports remain (full sweep), CPU mini suite green
  (202 passed, 2 skipped).
2026-07-04 17:15:34 +00:00

203 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Wan2.1-1.3B ModelCard (design_v3 §4) — the (recipe, runtime) pair for T2V.
Mirrors the real Wan2.1-1.3B wiring discovered in the repo:
* DiT ``fastvideo.models.dits.wanvideo:WanTransformer3DModel`` (40 layers, dim 5120)
* VAE ``fastvideo.models.vaes.wanvae:AutoencoderKLWan`` (z=16, 4×/8× compression)
* T5 ``fastvideo.models.encoders.t5:T5EncoderModel``
* sched ``FlowUniPCMultistepScheduler`` (flow_prediction; 480p shift 3.0, 720p 5.0)
``load_id`` records those for the GPU torch adapter; ``factory`` returns the CPU toy here.
"""
from __future__ import annotations
from v2._enums import Capability, ConsistencyLevel, LoopKind, WorkUnitKind
from v2.card import (
CacheContract,
CapabilityMatrix,
ComponentSpec,
CostModel,
LoopSpec,
ModelCard,
ParallelismContract,
ParitySpec,
ParityTestSpec,
PrecisionContract,
RecipeSpec,
)
from v2.loop.policies import (
BoundaryTimestepRouting,
ClassicCFG,
FlowShiftPolicy,
NoRouting,
PrecisionPolicy,
)
from v2.parallel import ParallelPlan
from v2.models.backend import ToyDiT, ToyTextEncoder, ToyVAE, _seed_from
from v2.models.wan21.loop import WanDenoiseLoop
def build_wan21_card(model_id: str = "wan2.1-1.3b", *, cfg_policy=None, flow_shift: float = 3.0,
checkpoint_root: str | None = None, latent_channels: int = 16,
spatial_ratio: int = 8, temporal_ratio: int = 4) -> ModelCard:
seed = _seed_from(model_id)
cost = CostModel(kind=WorkUnitKind.DIFFUSION_STEP, base_seconds=1e-4, per_unit_seconds=1e-7)
cfg = cfg_policy or ClassicCFG()
flow = FlowShiftPolicy(shift=flow_shift, bucket_lookup={480 * 832: 3.0, 720 * 1280: 5.0})
precision = PrecisionPolicy(compute_dtype="float32", scheduler_step_in_fp32=True)
expert = NoRouting("transformer")
def loop_factory():
return WanDenoiseLoop(loop_id="diffusion_denoise", cfg=cfg, flow_shift=flow,
precision=precision, expert=expert, cost=cost,
latent_channels=latent_channels, spatial_ratio=spatial_ratio,
temporal_ratio=temporal_ratio)
components = {
"text_encoder": ComponentSpec(
component_id="text_encoder", kind="text_encoder",
load_id="fastvideo.models.encoders.t5:T5EncoderModel",
factory=lambda inst: ToyTextEncoder(), required_for={"t2v"}),
"vae": ComponentSpec(
component_id="vae", kind="vae",
load_id="fastvideo.models.vaes.wanvae:AutoencoderKLWan",
factory=lambda inst: ToyVAE(), required_for={"t2v"}),
"transformer": ComponentSpec(
component_id="transformer", kind="dit",
load_id="fastvideo.models.dits.wanvideo:WanTransformer3DModel",
factory=lambda inst: ToyDiT(seed=seed),
resident_for=["diffusion_denoise"], required_for={"t2v"}),
}
loops = {
"diffusion_denoise": LoopSpec(
loop_id="diffusion_denoise", kind=LoopKind.DIFFUSION_DENOISE,
work_unit_kind=WorkUnitKind.DIFFUSION_STEP, step_cost_model=cost,
shared_weight_components=["transformer"], cache_policy=["feature"],
graph_capture="breakable_cudagraph", loop_factory=loop_factory),
}
card = ModelCard(
model_id=model_id, family="wan",
components=components, loops=loops,
capabilities=CapabilityMatrix.of(
Capability.TEXT_TO_VIDEO, # base Wan2.1 is T2V-only; i2v is the separate InP variant
Capability.VAE_DECODE, Capability.POLICY_ROLLOUT),
recipe=RecipeSpec(method="base", assumes_loop="diffusion_denoise",
assumes_precision="float32", consistency_required=ConsistencyLevel.C1),
parity=ParitySpec(consistency_levels=[ConsistencyLevel.C1], interleave_required=True,
tests=[ParityTestSpec(name="denoise_trajectory", level=ConsistencyLevel.C1,
tap="latents")]),
caches={"feature": CacheContract(cache_class="feature", max_bytes=1 << 24,
reuse_across_requests=True)},
precision=PrecisionContract(default_dtype="float32", training_precision="float32"),
parallelism=ParallelismContract(
valid_plans=[ParallelPlan.single(),
ParallelPlan(axes={"sp": 2, "cfgp": 2}, mesh_order=["cfgp", "sp"])],
default_plan=ParallelPlan.single()),
)
card.validate()
if checkpoint_root:
stamp_wan21_checkpoints(card, checkpoint_root)
return card
def build_wan22_ti2v_card(model_id: str = "wan2.2-ti2v-5b", *,
checkpoint_root: str | None = None) -> ModelCard:
"""Wan2.2-TI2V-5B card. Same component classes as Wan2.1 (WanTransformer3DModel / AutoencoderKLWan /
UMT5), so the torch adapters are reused as-is — the deltas are the higher-compression VAE geometry
(z_dim=48, 16x spatial, 4x temporal) and the 480p flow shift 5.0. The DiT forward accepts a scalar
timestep (its 1D path), so no per-frame ``expand_timesteps`` is needed for pure t2v. Uses the same
``transformer/vae/text_encoder`` subfolder layout, so ``stamp_wan21_checkpoints`` applies."""
return build_wan21_card(model_id=model_id, flow_shift=5.0, checkpoint_root=checkpoint_root,
latent_channels=48, spatial_ratio=16, temporal_ratio=4)
def build_wan22_a14b_card(model_id: str = "wan2.2-t2v-a14b", *, checkpoint_root: str | None = None,
boundary: float = 0.875) -> ModelCard:
"""Wan2.2-T2V-A14B card — MoE: two WanTransformer3DModel experts (in_ch=16, Wan2.1 16/8/4 geometry)
with a boundary-timestep switch (``boundary_ratio`` 0.875). ``transformer`` is the high-noise expert
(early, high-sigma steps), ``transformer_2`` the low-noise expert. ``BoundaryTimestepRouting`` picks
per step and ``WanDenoiseLoop`` dispatches via ``model.component(expert_id)`` (both experts reuse the
Wan torch adapter as-is). The boundary is in (shifted) sigma space: ctx.sigma>=0.875 -> high-noise,
matching diffusers' ``t >= boundary_ratio*num_train_timesteps`` on the shifted timesteps. NOTE: 2x14B
bf16 (~56GB) + UMT5 resident is near an 80GB GPU's limit — use modest res/frames."""
seed = _seed_from(model_id)
cost = CostModel(kind=WorkUnitKind.DIFFUSION_STEP, base_seconds=1e-4, per_unit_seconds=1e-7)
cfg = ClassicCFG()
flow = FlowShiftPolicy(shift=5.0, bucket_lookup={480 * 832: 3.0, 720 * 1280: 5.0})
precision = PrecisionPolicy(compute_dtype="float32", scheduler_step_in_fp32=True)
expert = BoundaryTimestepRouting(high_noise="transformer", low_noise="transformer_2", boundary=boundary)
def loop_factory():
return WanDenoiseLoop(loop_id="diffusion_denoise", cfg=cfg, flow_shift=flow,
precision=precision, expert=expert, cost=cost) # 16/8/4 default geometry
def _dit_spec(cid):
return ComponentSpec(
component_id=cid, kind="dit",
load_id="fastvideo.models.dits.wanvideo:WanTransformer3DModel",
factory=lambda inst: ToyDiT(seed=seed),
resident_for=["diffusion_denoise"], required_for={"t2v"})
components = {
"text_encoder": ComponentSpec(
component_id="text_encoder", kind="text_encoder",
load_id="fastvideo.models.encoders.t5:T5EncoderModel",
factory=lambda inst: ToyTextEncoder(), required_for={"t2v"}),
"vae": ComponentSpec(
component_id="vae", kind="vae",
load_id="fastvideo.models.vaes.wanvae:AutoencoderKLWan",
factory=lambda inst: ToyVAE(), required_for={"t2v"}),
"transformer": _dit_spec("transformer"),
"transformer_2": _dit_spec("transformer_2"),
}
loops = {
"diffusion_denoise": LoopSpec(
loop_id="diffusion_denoise", kind=LoopKind.DIFFUSION_DENOISE,
work_unit_kind=WorkUnitKind.DIFFUSION_STEP, step_cost_model=cost,
shared_weight_components=["transformer", "transformer_2"], cache_policy=["feature"],
graph_capture="breakable_cudagraph", loop_factory=loop_factory),
}
card = ModelCard(
model_id=model_id, family="wan",
components=components, loops=loops,
capabilities=CapabilityMatrix.of(
Capability.TEXT_TO_VIDEO, Capability.VAE_DECODE, Capability.POLICY_ROLLOUT),
recipe=RecipeSpec(method="base", assumes_loop="diffusion_denoise",
assumes_precision="float32", consistency_required=ConsistencyLevel.C1),
parity=ParitySpec(consistency_levels=[ConsistencyLevel.C1], interleave_required=True,
tests=[ParityTestSpec(name="denoise_trajectory", level=ConsistencyLevel.C1,
tap="latents")]),
caches={"feature": CacheContract(cache_class="feature", max_bytes=1 << 24,
reuse_across_requests=True)},
precision=PrecisionContract(default_dtype="float32", training_precision="float32"),
parallelism=ParallelismContract(valid_plans=[ParallelPlan.single()],
default_plan=ParallelPlan.single()),
)
card.validate()
if checkpoint_root:
stamp_wan21_checkpoints(card, checkpoint_root)
return card
# Diffusers checkpoint layout: each component's weights live in a subfolder of the model root. The stamp
# loop skips any subfolder a given card doesn't declare, so this superset covers Wan2.1, Wan2.2 MoE
# (``transformer_2``), and LTX-2 (``spatial_upsampler``) alike.
_WAN21_SUBFOLDERS = {"transformer": "transformer", "transformer_2": "transformer_2",
"vae": "vae", "text_encoder": "text_encoder",
"spatial_upsampler": "spatial_upsampler",
"audio_vae": "audio_vae", "vocoder": "vocoder"} # LTX-2.3 T2VS audio branch
def stamp_wan21_checkpoints(card: ModelCard, model_root: str) -> ModelCard:
"""GPU deploy-time: point each component's ``ComponentSpec.checkpoint`` at its weights subfolder
under ``model_root`` (a local diffusers dir, or an HF id resolved via ``snapshot_download``). The
toy cards leave ``checkpoint`` empty; the real torch adapters require it (BRINGUP risk A). The
adapter derives the pipeline-config model root from ``dirname(checkpoint)`` and loads the sibling
``tokenizer/`` directory too. Mutates and returns the card."""
import os
if not os.path.isdir(model_root):
from huggingface_hub import snapshot_download
model_root = snapshot_download(model_root)
for component_id, subfolder in _WAN21_SUBFOLDERS.items():
if component_id in card.components:
card.components[component_id].checkpoint = os.path.join(model_root, subfolder)
return card