返回 JoyAI-Echo
transformer.py
1 from dataclasses import dataclass, replace
2
3 import torch
4
5 from ltx_core.guidance.perturbations import BatchedPerturbationConfig, PerturbationType
6 from ltx_core.model.transformer.adaln import adaln_embedding_coefficient
7 from ltx_core.model.transformer.attention import Attention, AttentionCallable, AttentionFunction
8 from ltx_core.model.transformer.feed_forward import FeedForward
9 from ltx_core.model.transformer.rope import LTXRopeType
10 from ltx_core.model.transformer.transformer_args import TransformerArgs
11 from ltx_core.utils import rms_norm
12
13
14 @dataclass
15 class TransformerConfig:
16 dim: int
17 heads: int
18 d_head: int
19 context_dim: int
20 apply_gated_attention: bool = False
21 cross_attention_adaln: bool = False
22
23
24 class BasicAVTransformerBlock(torch.nn.Module):
25 def __init__(
26 self,
27 idx: int,
28 num_layers: int,
29 video: TransformerConfig | None = None,
30 audio: TransformerConfig | None = None,
31 rope_type: LTXRopeType = LTXRopeType.INTERLEAVED,
32 norm_eps: float = 1e-6,
33 attention_function: AttentionFunction | AttentionCallable = AttentionFunction.DEFAULT,
34 ):
35 super().__init__()
36
37 self.idx = idx
38 self.num_layers = num_layers
39 if video is not None:
40 self.attn1 = Attention(
41 query_dim=video.dim,
42 heads=video.heads,
43 dim_head=video.d_head,
44 context_dim=None,
45 rope_type=rope_type,
46 norm_eps=norm_eps,
47 attention_function=attention_function,
48 apply_gated_attention=video.apply_gated_attention,
49 )
50 self.attn2 = Attention(
51 query_dim=video.dim,
52 context_dim=video.context_dim,
53 heads=video.heads,
54 dim_head=video.d_head,
55 rope_type=rope_type,
56 norm_eps=norm_eps,
57 attention_function=attention_function,
58 apply_gated_attention=video.apply_gated_attention,
59 )
60 self.ff = FeedForward(video.dim, dim_out=video.dim)
61 video_sst_size = adaln_embedding_coefficient(video.cross_attention_adaln)
62 self.scale_shift_table = torch.nn.Parameter(torch.empty(video_sst_size, video.dim))
63
64 if audio is not None:
65 self.audio_attn1 = Attention(
66 query_dim=audio.dim,
67 heads=audio.heads,
68 dim_head=audio.d_head,
69 context_dim=None,
70 rope_type=rope_type,
71 norm_eps=norm_eps,
72 attention_function=attention_function,
73 apply_gated_attention=audio.apply_gated_attention,
74 )
75 self.audio_attn2 = Attention(
76 query_dim=audio.dim,
77 context_dim=audio.context_dim,
78 heads=audio.heads,
79 dim_head=audio.d_head,
80 rope_type=rope_type,
81 norm_eps=norm_eps,
82 attention_function=attention_function,
83 apply_gated_attention=audio.apply_gated_attention,
84 )
85 self.audio_ff = FeedForward(audio.dim, dim_out=audio.dim)
86 audio_sst_size = adaln_embedding_coefficient(audio.cross_attention_adaln)
87 self.audio_scale_shift_table = torch.nn.Parameter(torch.empty(audio_sst_size, audio.dim))
88
89 if audio is not None and video is not None:
90 # Q: Video, K,V: Audio
91 self.audio_to_video_attn = Attention(
92 query_dim=video.dim,
93 context_dim=audio.dim,
94 heads=audio.heads,
95 dim_head=audio.d_head,
96 rope_type=rope_type,
97 norm_eps=norm_eps,
98 attention_function=attention_function,
99 apply_gated_attention=video.apply_gated_attention,
100 )
101
102 # Q: Audio, K,V: Video
103 self.video_to_audio_attn = Attention(
104 query_dim=audio.dim,
105 context_dim=video.dim,
106 heads=audio.heads,
107 dim_head=audio.d_head,
108 rope_type=rope_type,
109 norm_eps=norm_eps,
110 attention_function=attention_function,
111 apply_gated_attention=audio.apply_gated_attention,
112 )
113
114 self.scale_shift_table_a2v_ca_audio = torch.nn.Parameter(torch.empty(5, audio.dim))
115 self.scale_shift_table_a2v_ca_video = torch.nn.Parameter(torch.empty(5, video.dim))
116
117 self.cross_attention_adaln = (video is not None and video.cross_attention_adaln) or (
118 audio is not None and audio.cross_attention_adaln
119 )
120
121 if self.cross_attention_adaln and video is not None:
122 self.prompt_scale_shift_table = torch.nn.Parameter(torch.empty(2, video.dim))
123 if self.cross_attention_adaln and audio is not None:
124 self.audio_prompt_scale_shift_table = torch.nn.Parameter(torch.empty(2, audio.dim))
125
126 self.norm_eps = norm_eps
127
128 def get_ada_values(
129 self, scale_shift_table: torch.Tensor, batch_size: int, timestep: torch.Tensor, indices: slice
130 ) -> tuple[torch.Tensor, ...]:
131 num_ada_params = scale_shift_table.shape[0]
132
133 ada_values = (
134 scale_shift_table[indices].unsqueeze(0).unsqueeze(0).to(device=timestep.device, dtype=timestep.dtype)
135 + timestep.reshape(batch_size, timestep.shape[1], num_ada_params, -1)[:, :, indices, :]
136 ).unbind(dim=2)
137 return ada_values
138
139 def get_av_ca_ada_values(
140 self,
141 scale_shift_table: torch.Tensor,
142 batch_size: int,
143 scale_shift_timestep: torch.Tensor,
144 gate_timestep: torch.Tensor,
145 scale_shift_indices: slice,
146 num_scale_shift_values: int = 4,
147 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
148 scale_shift_ada_values = self.get_ada_values(
149 scale_shift_table[:num_scale_shift_values, :], batch_size, scale_shift_timestep, scale_shift_indices
150 )
151 gate_ada_values = self.get_ada_values(
152 scale_shift_table[num_scale_shift_values:, :], batch_size, gate_timestep, slice(None, None)
153 )
154
155 scale, shift = (t.squeeze(2) for t in scale_shift_ada_values)
156 (gate,) = (t.squeeze(2) for t in gate_ada_values)
157
158 return scale, shift, gate
159
160 def _apply_text_cross_attention(
161 self,
162 x: torch.Tensor,
163 context: torch.Tensor,
164 attn: AttentionCallable,
165 scale_shift_table: torch.Tensor,
166 prompt_scale_shift_table: torch.Tensor | None,
167 timestep: torch.Tensor,
168 prompt_timestep: torch.Tensor | None,
169 context_mask: torch.Tensor | None,
170 cross_attention_adaln: bool = False,
171 ) -> torch.Tensor:
172 """Apply text cross-attention, with optional AdaLN modulation."""
173 if cross_attention_adaln:
174 shift_q, scale_q, gate = self.get_ada_values(scale_shift_table, x.shape[0], timestep, slice(6, 9))
175 return apply_cross_attention_adaln(
176 x,
177 context,
178 attn,
179 shift_q,
180 scale_q,
181 gate,
182 prompt_scale_shift_table,
183 prompt_timestep,
184 context_mask,
185 self.norm_eps,
186 )
187 return attn(rms_norm(x, eps=self.norm_eps), context=context, mask=context_mask)
188
189 def forward( # noqa: PLR0915
190 self,
191 video: TransformerArgs | None,
192 audio: TransformerArgs | None,
193 perturbations: BatchedPerturbationConfig | None = None,
194 ) -> tuple[TransformerArgs | None, TransformerArgs | None]:
195 if video is None and audio is None:
196 raise ValueError("At least one of video or audio must be provided")
197
198 batch_size = (video or audio).x.shape[0]
199
200 if perturbations is None:
201 perturbations = BatchedPerturbationConfig.empty(batch_size)
202
203 vx = video.x if video is not None else None
204 ax = audio.x if audio is not None else None
205
206 run_vx = video is not None and video.enabled and vx.numel() > 0
207 run_ax = audio is not None and audio.enabled and ax.numel() > 0
208
209 run_a2v = run_vx and (audio is not None and ax.numel() > 0)
210 run_v2a = run_ax and (video is not None and vx.numel() > 0)
211
212 if run_vx:
213 vshift_msa, vscale_msa, vgate_msa = self.get_ada_values(
214 self.scale_shift_table, vx.shape[0], video.timesteps, slice(0, 3)
215 )
216 norm_vx = rms_norm(vx, eps=self.norm_eps) * (1 + vscale_msa) + vshift_msa
217 del vshift_msa, vscale_msa
218
219 all_perturbed = perturbations.all_in_batch(PerturbationType.SKIP_VIDEO_SELF_ATTN, self.idx)
220 none_perturbed = not perturbations.any_in_batch(PerturbationType.SKIP_VIDEO_SELF_ATTN, self.idx)
221 v_mask = (
222 perturbations.mask_like(PerturbationType.SKIP_VIDEO_SELF_ATTN, self.idx, vx)
223 if not all_perturbed and not none_perturbed
224 else None
225 )
226 vx = (
227 vx
228 + self.attn1(
229 norm_vx,
230 pe=video.positional_embeddings,
231 mask=video.self_attention_mask,
232 perturbation_mask=v_mask,
233 all_perturbed=all_perturbed,
234 )
235 * vgate_msa
236 )
237 del vgate_msa, norm_vx, v_mask
238 vx = vx + self._apply_text_cross_attention(
239 vx,
240 video.context,
241 self.attn2,
242 self.scale_shift_table,
243 getattr(self, "prompt_scale_shift_table", None),
244 video.timesteps,
245 video.prompt_timestep,
246 video.context_mask,
247 cross_attention_adaln=self.cross_attention_adaln,
248 )
249
250 if run_ax:
251 ashift_msa, ascale_msa, agate_msa = self.get_ada_values(
252 self.audio_scale_shift_table, ax.shape[0], audio.timesteps, slice(0, 3)
253 )
254
255 norm_ax = rms_norm(ax, eps=self.norm_eps) * (1 + ascale_msa) + ashift_msa
256 del ashift_msa, ascale_msa
257 all_perturbed = perturbations.all_in_batch(PerturbationType.SKIP_AUDIO_SELF_ATTN, self.idx)
258 none_perturbed = not perturbations.any_in_batch(PerturbationType.SKIP_AUDIO_SELF_ATTN, self.idx)
259 a_mask = (
260 perturbations.mask_like(PerturbationType.SKIP_AUDIO_SELF_ATTN, self.idx, ax)
261 if not all_perturbed and not none_perturbed
262 else None
263 )
264 ax = (
265 ax
266 + self.audio_attn1(
267 norm_ax,
268 pe=audio.positional_embeddings,
269 mask=audio.self_attention_mask,
270 perturbation_mask=a_mask,
271 all_perturbed=all_perturbed,
272 )
273 * agate_msa
274 )
275 del agate_msa, norm_ax, a_mask
276 ax = ax + self._apply_text_cross_attention(
277 ax,
278 audio.context,
279 self.audio_attn2,
280 self.audio_scale_shift_table,
281 getattr(self, "audio_prompt_scale_shift_table", None),
282 audio.timesteps,
283 audio.prompt_timestep,
284 audio.context_mask,
285 cross_attention_adaln=self.cross_attention_adaln,
286 )
287
288 # Audio - Video cross attention.
289 if run_a2v or run_v2a:
290 vx_norm3 = rms_norm(vx, eps=self.norm_eps)
291 ax_norm3 = rms_norm(ax, eps=self.norm_eps)
292
293 if run_a2v and not perturbations.all_in_batch(PerturbationType.SKIP_A2V_CROSS_ATTN, self.idx):
294 scale_ca_video_a2v, shift_ca_video_a2v, gate_out_a2v = self.get_av_ca_ada_values(
295 self.scale_shift_table_a2v_ca_video,
296 vx.shape[0],
297 video.cross_scale_shift_timestep,
298 video.cross_gate_timestep,
299 slice(0, 2),
300 )
301 vx_scaled = vx_norm3 * (1 + scale_ca_video_a2v) + shift_ca_video_a2v
302 del scale_ca_video_a2v, shift_ca_video_a2v
303
304 scale_ca_audio_a2v, shift_ca_audio_a2v, _ = self.get_av_ca_ada_values(
305 self.scale_shift_table_a2v_ca_audio,
306 ax.shape[0],
307 audio.cross_scale_shift_timestep,
308 audio.cross_gate_timestep,
309 slice(0, 2),
310 )
311 ax_scaled = ax_norm3 * (1 + scale_ca_audio_a2v) + shift_ca_audio_a2v
312 del scale_ca_audio_a2v, shift_ca_audio_a2v
313 a2v_mask = perturbations.mask_like(PerturbationType.SKIP_A2V_CROSS_ATTN, self.idx, vx)
314 vx = vx + (
315 self.audio_to_video_attn(
316 vx_scaled,
317 context=ax_scaled,
318 pe=video.cross_positional_embeddings,
319 k_pe=audio.cross_positional_embeddings,
320 )
321 * gate_out_a2v
322 * a2v_mask
323 )
324 del gate_out_a2v, a2v_mask, vx_scaled, ax_scaled
325
326 if run_v2a and not perturbations.all_in_batch(PerturbationType.SKIP_V2A_CROSS_ATTN, self.idx):
327 scale_ca_audio_v2a, shift_ca_audio_v2a, gate_out_v2a = self.get_av_ca_ada_values(
328 self.scale_shift_table_a2v_ca_audio,
329 ax.shape[0],
330 audio.cross_scale_shift_timestep,
331 audio.cross_gate_timestep,
332 slice(2, 4),
333 )
334 ax_scaled = ax_norm3 * (1 + scale_ca_audio_v2a) + shift_ca_audio_v2a
335 del scale_ca_audio_v2a, shift_ca_audio_v2a
336 scale_ca_video_v2a, shift_ca_video_v2a, _ = self.get_av_ca_ada_values(
337 self.scale_shift_table_a2v_ca_video,
338 vx.shape[0],
339 video.cross_scale_shift_timestep,
340 video.cross_gate_timestep,
341 slice(2, 4),
342 )
343 vx_scaled = vx_norm3 * (1 + scale_ca_video_v2a) + shift_ca_video_v2a
344 del scale_ca_video_v2a, shift_ca_video_v2a
345 v2a_mask = perturbations.mask_like(PerturbationType.SKIP_V2A_CROSS_ATTN, self.idx, ax)
346 v2a_update = (
347 self.video_to_audio_attn(
348 ax_scaled,
349 context=vx_scaled,
350 pe=audio.cross_positional_embeddings,
351 k_pe=video.cross_positional_embeddings,
352 )
353 * gate_out_v2a
354 * v2a_mask
355 )
356 ax = ax + v2a_update
357 del gate_out_v2a, v2a_mask, ax_scaled, vx_scaled, v2a_update
358
359 del vx_norm3, ax_norm3
360
361 if run_vx:
362 vshift_mlp, vscale_mlp, vgate_mlp = self.get_ada_values(
363 self.scale_shift_table, vx.shape[0], video.timesteps, slice(3, 6)
364 )
365 vx_scaled = rms_norm(vx, eps=self.norm_eps) * (1 + vscale_mlp) + vshift_mlp
366 vx = vx + self.ff(vx_scaled) * vgate_mlp
367
368 del vshift_mlp, vscale_mlp, vgate_mlp, vx_scaled
369
370 if run_ax:
371 ashift_mlp, ascale_mlp, agate_mlp = self.get_ada_values(
372 self.audio_scale_shift_table, ax.shape[0], audio.timesteps, slice(3, 6)
373 )
374 ax_scaled = rms_norm(ax, eps=self.norm_eps) * (1 + ascale_mlp) + ashift_mlp
375 ax = ax + self.audio_ff(ax_scaled) * agate_mlp
376
377 del ashift_mlp, ascale_mlp, agate_mlp, ax_scaled
378
379 return replace(video, x=vx) if video is not None else None, replace(audio, x=ax) if audio is not None else None
380
381
382 def apply_cross_attention_adaln(
383 x: torch.Tensor,
384 context: torch.Tensor,
385 attn: AttentionCallable,
386 q_shift: torch.Tensor,
387 q_scale: torch.Tensor,
388 q_gate: torch.Tensor,
389 prompt_scale_shift_table: torch.Tensor,
390 prompt_timestep: torch.Tensor,
391 context_mask: torch.Tensor | None = None,
392 norm_eps: float = 1e-6,
393 ) -> torch.Tensor:
394 batch_size = x.shape[0]
395 shift_kv, scale_kv = (
396 prompt_scale_shift_table[None, None].to(device=x.device, dtype=x.dtype)
397 + prompt_timestep.reshape(batch_size, prompt_timestep.shape[1], 2, -1)
398 ).unbind(dim=2)
399 attn_input = rms_norm(x, eps=norm_eps) * (1 + q_scale) + q_shift
400 encoder_hidden_states = context * (1 + scale_kv) + shift_kv
401 return attn(attn_input, context=encoder_hidden_states, mask=context_mask) * q_gate
402
402 lines PYTHON