Skip to content

Commit c2d7143

Browse files
[feat] LTX-2.3 transformer support (config-gated extension of LTX-2) (#1397)
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent afdb6fb commit c2d7143

9 files changed

Lines changed: 843 additions & 86 deletions

File tree

fastvideo/configs/models/dits/ltx2.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,15 @@ class LTX2VideoArchConfig(DiTArchConfig):
2222
_fsdp_shard_conditions: list = field(default_factory=lambda: [is_ltx2_blocks])
2323
_compile_conditions: list = field(default_factory=lambda: [is_ltx2_blocks])
2424

25-
# Parameter name mapping for weight conversion (hf/comfy -> FastVideo)
25+
# Parameter name mapping for weight conversion (hf/comfy -> FastVideo).
26+
# The ``to_gate_compress`` -> ``to_gate_logits`` rules for the LTX-2.3
27+
# gated-attention path are inserted at the front of this dict in
28+
# ``__post_init__`` only when ``apply_gated_attention=True``. Without
29+
# that flag the target model has no ``to_gate_logits`` slot, *and* the
30+
# same-named ``to_gate_compress`` already lives on the LTX-2.0 VSA-QAT
31+
# gate path (plus it is in the default ``lora_target_modules`` list).
32+
# Applying the rename unconditionally silently breaks LTX-2.0 VSA
33+
# checkpoints and default-target LoRAs.
2634
param_names_mapping: dict = field(
2735
default_factory=lambda: {
2836
r"^model\.diffusion_model\.(.*)$": r"model.\1",
@@ -44,6 +52,9 @@ class LTX2VideoArchConfig(DiTArchConfig):
4452
attention_type: str = "default"
4553
rope_type: str = "split"
4654
double_precision_rope: bool = True
55+
# LTX-2.3 gated extensions. All default OFF == LTX-2.0 behavior.
56+
cross_attention_adaln: bool = False
57+
caption_proj_before_connector: bool = False
4758

4859
positional_embedding_theta: float = 10000.0
4960
positional_embedding_max_pos: list[int] = field(default_factory=lambda: [20, 2048, 2048])
@@ -64,6 +75,27 @@ class LTX2VideoArchConfig(DiTArchConfig):
6475
audio_cross_attention_dim: int = 2048
6576
audio_positional_embedding_max_pos: list[int] = field(default_factory=lambda: [20])
6677
av_ca_timestep_scale_multiplier: int = 1
78+
# LTX-2.3 gated self-attention (distinct from the VSA-QAT to_gate_compress
79+
# gate). Default OFF == LTX-2.0 behavior.
80+
apply_gated_attention: bool = False
81+
82+
# Text connector/feature extractor compatibility fields carried in some
83+
# transformer configs (used by the LTX-2.3 text stack). Defaults match
84+
# the LTX-2.0 connector layout.
85+
caption_projection_first_linear: bool = True
86+
caption_proj_input_norm: bool = True
87+
caption_projection_second_linear: bool = True
88+
connector_num_attention_heads: int = 30
89+
connector_attention_head_dim: int = 128
90+
connector_num_layers: int = 2
91+
audio_connector_num_attention_heads: int = 30
92+
audio_connector_attention_head_dim: int = 128
93+
audio_connector_num_layers: int = 2
94+
95+
# STG perturbation block index differs across model versions.
96+
# LTX-2.0 defaults to block 29; LTX-2.3 (caption_proj_before_connector)
97+
# uses block 28. ``None`` resolves in __post_init__.
98+
stg_block_idx: int | None = None
6799

68100
def __post_init__(self):
69101
super().__post_init__()
@@ -72,6 +104,29 @@ def __post_init__(self):
72104
self.in_channels = self.num_channels_latents * patch_volume
73105
if self.out_channels is None:
74106
self.out_channels = self.in_channels
107+
if self.stg_block_idx is None:
108+
self.stg_block_idx = 28 if self.caption_proj_before_connector else 29
109+
110+
# LTX-2.3 stores the gated-attention weight under ``to_gate_compress``
111+
# upstream; FastVideo's internal name is ``to_gate_logits``. Only
112+
# enable the rename when the gated path is actually configured: the
113+
# LTX-2.0 attention module's own ``to_gate_compress`` parameter
114+
# (created when the backend is ``VIDEO_SPARSE_ATTN``) and the default
115+
# ``to_gate_compress`` LoRA target both share the upstream name, so
116+
# an unconditional rename would silently retarget them. Inserted at
117+
# the front so first-match-wins matching fires the rename before the
118+
# generic prefix-strip rules.
119+
if self.apply_gated_attention:
120+
gate_rules = {
121+
r"^model\.diffusion_model\.(.*)\.to_gate_compress\.(.*)$": r"model.\1.to_gate_logits.\2",
122+
r"^diffusion_model\.(.*)\.to_gate_compress\.(.*)$": r"model.\1.to_gate_logits.\2",
123+
r"^model\.(.*)\.to_gate_compress\.(.*)$": r"model.\1.to_gate_logits.\2",
124+
r"^(.*)\.to_gate_compress\.(.*)$": r"model.\1.to_gate_logits.\2",
125+
}
126+
self.param_names_mapping = {
127+
**gate_rules,
128+
**self.param_names_mapping,
129+
}
75130

76131

77132
@dataclass

fastvideo/configs/models/encoders/gemma.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88

99

1010
def _is_feature_extractor_linear(n: str, m) -> bool:
11-
return n.endswith("feature_extractor_linear")
11+
# LTX-2.3 (caption_proj_before_connector) introduces separate
12+
# video/audio feature extractor linears; keep the LTX-2.0 name too.
13+
return (
14+
n.endswith("feature_extractor_linear")
15+
or n.endswith("video_feature_extractor_linear")
16+
or n.endswith("audio_feature_extractor_linear")
17+
)
1218

1319

1420
def _is_embeddings(n: str, m) -> bool:
@@ -35,14 +41,26 @@ class LTX2GemmaArchConfig(TextEncoderArchConfig):
3541

3642
feature_extractor_in_features: int = 3840 * 49
3743
feature_extractor_out_features: int = 3840
44+
# LTX-2.3 text-stack connector fields (default OFF == LTX-2.0 behavior).
45+
video_feature_extractor_out_features: int | None = None
46+
audio_feature_extractor_out_features: int | None = None
47+
caption_proj_before_connector: bool = False
48+
caption_projection_first_linear: bool = True
49+
caption_proj_input_norm: bool = True
50+
caption_projection_second_linear: bool = True
3851

3952
connector_num_attention_heads: int = 30
4053
connector_attention_head_dim: int = 128
4154
connector_num_layers: int = 2
55+
# Separate audio connector geometry (None falls back to the video values).
56+
audio_connector_num_attention_heads: int | None = None
57+
audio_connector_attention_head_dim: int | None = None
58+
audio_connector_num_layers: int | None = None
4259
connector_positional_embedding_theta: float = 10000.0
4360
connector_positional_embedding_max_pos: list[int] = field(default_factory=lambda: [4096])
4461
connector_rope_type: str = "split"
4562
connector_double_precision_rope: bool = False
63+
connector_apply_gated_attention: bool = False
4664
connector_num_learnable_registers: int | None = 128
4765

4866
_fsdp_shard_conditions: list = field(

0 commit comments

Comments
 (0)