[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).
This commit is contained in:
SolitaryThinker
2026-07-04 17:15:34 +00:00
committed by Peiyuan Zhang
parent 1d23399d81
commit 6096b00aeb
5 changed files with 41 additions and 3 deletions
+2 -1
View File
@@ -182,7 +182,8 @@ def build_wan22_a14b_card(model_id: str = "wan2.2-t2v-a14b", *, checkpoint_root:
# (``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"}
"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:
+19
View File
@@ -363,3 +363,22 @@ def build_torch_upsampler(spec, instance, platform):
stats_owner = getattr(vae_module, "decoder", None) or getattr(vae_module, "encoder", None) or vae_module
from .torch_ltx2 import TorchLTX2Upsampler
return TorchLTX2Upsampler(module, stats_owner, device=device, dtype=dtype)
def build_torch_audio_vae(spec, instance, platform):
ckpt = _require_checkpoint(spec)
_ensure_fastvideo_runtime()
module = _load_component("AudioDecoderLoader", ckpt, _fastvideo_args(spec)) # LTX2AudioDecoder
device, dtype = _device(platform), _native_dtype(module)
voc = instance.component("vocoder") # build/fetch the vocoder; TorchLTX2AudioVAE chains decoder->vocoder
from .torch_ltx2 import TorchLTX2AudioVAE
return TorchLTX2AudioVAE(module, voc, device=device, dtype=dtype)
def build_torch_vocoder(spec, instance, platform):
ckpt = _require_checkpoint(spec)
_ensure_fastvideo_runtime()
module = _load_component("VocoderLoader", ckpt, _fastvideo_args(spec)) # LTX2Vocoder
device, dtype = _device(platform), _native_dtype(module)
from .torch_ltx2 import TorchLTX2Vocoder
return TorchLTX2Vocoder(module, device=device, dtype=dtype)
+14
View File
@@ -60,6 +60,16 @@ def _build_upsampler(spec, instance, platform):
return build_torch_upsampler(spec, instance, platform)
def _build_audio_vae(spec, instance, platform):
from .torch_adapters import build_torch_audio_vae
return build_torch_audio_vae(spec, instance, platform)
def _build_vocoder(spec, instance, platform):
from .torch_adapters import build_torch_vocoder
return build_torch_vocoder(spec, instance, platform)
def _flow_match_cuda(*args, **kwargs):
from .torch_kernels import flow_match_step
return flow_match_step(*args, **kwargs)
@@ -80,6 +90,10 @@ register_component("text_encoder", _build_text_encoder, device="cuda", available
source=f"{_ADAPTERS}:TorchT5Encoder")
register_component("upsampler", _build_upsampler, device="cuda", available=_cuda_available,
source=f"{_ADAPTERS}:TorchLTX2Upsampler")
register_component("audio_vae", _build_audio_vae, device="cuda", available=_cuda_available,
source=f"{_ADAPTERS}:TorchLTX2AudioVAE")
register_component("vocoder", _build_vocoder, device="cuda", available=_cuda_available,
source=f"{_ADAPTERS}:TorchLTX2Vocoder")
# --- solver ops: plain torch elementwise (no fused solver kernel exists in fastvideo-kernel) ---- #
# arch="generic" (elementwise, arch-agnostic), workspace_bytes=0 (no static scratch).
+4 -1
View File
@@ -139,7 +139,10 @@ class TorchGemma:
mask = toks.attention_mask.to(self.device)
from fastvideo.forward_context import set_forward_context
with set_forward_context(current_timestep=0, attn_metadata=None):
out = self.module(input_ids=ids, attention_mask=mask)
# output_hidden_states=True populates hidden_states=(audio_encoding,) — the SEPARATE audio
# connector projection (gemma.py:703); without it hidden_states is None and we'd wrongly
# fall back to the video embedding (different dim -> cross-attn shape mismatch).
out = self.module(input_ids=ids, attention_mask=mask, output_hidden_states=True)
video = out.last_hidden_state if hasattr(out, "last_hidden_state") else out[0]
hs = getattr(out, "hidden_states", None)
audio = hs[0] if hs else video
+2 -1
View File
@@ -42,7 +42,8 @@ def _make_cuda_available(monkeypatch):
# --------------------------------------------------------------------------- #
def test_cuda_has_all_components_and_two_solver_ops():
comps = sorted(r["kind"] for r in component_matrix() if r["device"] == "cuda")
assert comps == ["dit", "text_encoder", "upsampler", "vae"] # + upsampler: LTX-2 spatial upsampler
# + 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"]