Files
hao-ai-lab-FastVideo/v2/tests/test_torch_backend.py
T
SolitaryThinker 6096b00aeb [feat] v2 LTX-2.3 T2VS GPU-verified: audio VAE/vocoder wiring + dual-connector audio fix
The full joint text->video+audio LTX-2.3 path now generates on the real 18.99B model:
- GPU audio components: build_torch_audio_vae (AudioDecoderLoader -> LTX2AudioDecoder, chains the
  vocoder) + build_torch_vocoder (VocoderLoader -> LTX2Vocoder); registered the 'audio_vae'/'vocoder'
  cuda component kinds; stamped their checkpoint subfolders (_WAN21_SUBFOLDERS).
- Fix: TorchGemma.encode_av must pass output_hidden_states=True — the 2.3 connector's SEPARATE audio
  projection lives in hidden_states[0] only then (gemma.py:703); without it the audio text fell back to
  the video embedding (4096 vs 2048 -> audio cross-attn shape mismatch).
- GPU-verified T2VS: video (3,33,256,384, std 0.68) + audio (stereo 2x61920 @24kHz, nonzero, std 0.059).
- test_torch_backend: cuda component kinds now include audio_vae + vocoder. CPU suite green (202+2).
2026-07-04 17:15:34 +00:00

110 lines
5.2 KiB
Python

"""GPU torch backend — the CPU-verifiable tier (design_v3 §17).
The torch adapters/kernels themselves need a GPU box (see GPU_BRINGUP.md). What IS verifiable on a
torchless laptop is the wiring that must be right for the GPU path to even resolve correctly: the
cells are registered with honest sources, importing the backends never imports torch, a cuda
platform resolves the REAL cells (not a silent toy fallback), and building without torch fails loudly
rather than quietly returning a toy. Those are exactly the bugs that would otherwise surface only on
the box — so they're worth pinning here.
"""
from __future__ import annotations
import importlib.util
import sys
import pytest
from v2.card import load_card
from v2.models.wan21 import build_wan21_card
from v2.platform import (
COMPONENTS,
FLOW_MATCH_STEP,
FLOW_SDE_STEP,
KERNELS,
Platform,
component_matrix,
ensure_backends_loaded,
kernel_matrix,
)
def _make_cuda_available(monkeypatch):
"""Flip every registered cuda cell to 'available' (auto-restored), so resolution can be tested
without torch actually present."""
ensure_backends_loaded()
for reg in (*COMPONENTS._reg.values(), *KERNELS._reg.values()):
if reg.key[1] == "cuda":
monkeypatch.setattr(reg, "available", lambda: True)
# --------------------------------------------------------------------------- #
# Registration + honest sources #
# --------------------------------------------------------------------------- #
def test_cuda_has_all_components_and_two_solver_ops():
comps = sorted(r["kind"] for r in component_matrix() if r["device"] == "cuda")
# + upsampler (LTX-2 spatial), audio_vae + vocoder (LTX-2.3 T2VS audio branch)
assert comps == ["audio_vae", "dit", "text_encoder", "upsampler", "vae", "vocoder"]
ops = sorted(r["op"] for r in kernel_matrix() if r["device"] == "cuda")
assert ops == ["flow_match_step", "flow_sde_step"]
def test_cuda_solver_source_is_honest_no_fake_kernel():
"""fastvideo-kernel ships NO fused solver kernel — the cuda solver is plain torch. The source
must say so, not claim a 'fastvideo-kernel:flow_*' that does not exist."""
for r in kernel_matrix():
if r["device"] == "cuda":
assert "torch elementwise" in r["source"]
assert "fastvideo-kernel" not in r["source"]
assert r["arch"] == "generic" # elementwise: arch-agnostic, no static scratch
def test_cuda_cells_unavailable_on_this_box():
assert all(not r["available"] for r in component_matrix() if r["device"] == "cuda")
assert all(not r["available"] for r in kernel_matrix() if r["device"] == "cuda")
# --------------------------------------------------------------------------- #
# The load-bearing guard: importing the backends never imports torch #
# --------------------------------------------------------------------------- #
@pytest.mark.skipif(
importlib.util.find_spec("torch") is not None,
reason="torch installed (GPU box): the cuda-availability probe imports torch by design to call "
"torch.cuda.is_available(); this no-torch-import invariant is only verifiable without torch.")
def test_loading_backends_does_not_import_torch():
ensure_backends_loaded()
component_matrix(); kernel_matrix()
assert "torch" not in sys.modules
assert "v2.platform.backends.torch_adapters" not in sys.modules # imported only inside builders
assert "v2.platform.backends.torch_kernels" not in sys.modules
# --------------------------------------------------------------------------- #
# Resolution: when cuda IS available, the REAL cells win (no silent toy) #
# --------------------------------------------------------------------------- #
def test_cuda_platform_resolves_real_cells_not_toy(monkeypatch):
_make_cuda_available(monkeypatch)
p = Platform.cuda("sm90")
for kind in ("dit", "vae", "text_encoder"):
reg = COMPONENTS.resolve_first(p._component_keys(kind, "default"))
assert reg is not None and reg.key[1] == "cuda", kind # the cuda adapter, not the cpu toy
reg = p.resolve_kernel(FLOW_MATCH_STEP)
assert reg.key[1] == "cuda" and "torch elementwise" in reg.source
assert p.resolve_kernel(FLOW_SDE_STEP).key[1] == "cuda"
def test_cuda_build_fails_loudly_without_torch(monkeypatch):
"""With cuda 'available' but torch absent, building a component must RAISE (the lazy import of the
torch adapter fails) — never silently hand back a numpy toy decoding real latents."""
_make_cuda_available(monkeypatch)
inst = load_card(build_wan21_card(), cache_manager=None, platform=Platform.cuda("sm90"))
with pytest.raises((ImportError, ModuleNotFoundError, RuntimeError)):
inst.component("transformer")
# --------------------------------------------------------------------------- #
# The checkpoint seam (risk A) is additive — toy cards are unaffected #
# --------------------------------------------------------------------------- #
def test_checkpoint_field_defaults_empty_on_toy_cards():
card = build_wan21_card()
assert all(spec.checkpoint == "" for spec in card.components.values()) # CPU toys need no weights