返回 JoyAI-Echo
transformer.py
根目录 / ltx-core / src / ltx_core / model / transformer / 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 audio_self_attention_mask = audio.self_attention_mask
265 if self.idx >= int(self.num_layers * 0.7):
266 audio_self_attention_mask = audio.late_self_attention_mask
267 ax = (
268 ax
269 + self.audio_attn1(
270 norm_ax,
271 pe=audio.positional_embeddings,
272 mask=audio_self_attention_mask,
273 perturbation_mask=a_mask,
274 all_perturbed=all_perturbed,
275 )
276 * agate_msa
277 )
278 del agate_msa, norm_ax, a_mask
279 ax = ax + self._apply_text_cross_attention(
280 ax,
281 audio.context,
282 self.audio_attn2,
283 self.audio_scale_shift_table,
284 getattr(self, "audio_prompt_scale_shift_table", None),
285 audio.timesteps,
286 audio.prompt_timestep,
287 audio.context_mask,
288 cross_attention_adaln=self.cross_attention_adaln,
289 )
290
291 # Audio - Video cross attention.
292 if run_a2v or run_v2a:
293 vx_norm3 = rms_norm(vx, eps=self.norm_eps)
294 ax_norm3 = rms_norm(ax, eps=self.norm_eps)
295
296 if run_a2v and not perturbations.all_in_batch(PerturbationType.SKIP_A2V_CROSS_ATTN, self.idx):
297 scale_ca_video_a2v, shift_ca_video_a2v, gate_out_a2v = self.get_av_ca_ada_values(
298 self.scale_shift_table_a2v_ca_video,
299 vx.shape[0],
300 video.cross_scale_shift_timestep,
301 video.cross_gate_timestep,
302 slice(0, 2),
303 )
304 vx_scaled = vx_norm3 * (1 + scale_ca_video_a2v) + shift_ca_video_a2v
305 del scale_ca_video_a2v, shift_ca_video_a2v
306
307 scale_ca_audio_a2v, shift_ca_audio_a2v, _ = self.get_av_ca_ada_values(
308 self.scale_shift_table_a2v_ca_audio,
309 ax.shape[0],
310 audio.cross_scale_shift_timestep,
311 audio.cross_gate_timestep,
312 slice(0, 2),
313 )
314 ax_scaled = ax_norm3 * (1 + scale_ca_audio_a2v) + shift_ca_audio_a2v
315 del scale_ca_audio_a2v, shift_ca_audio_a2v
316 a2v_mask = perturbations.mask_like(PerturbationType.SKIP_A2V_CROSS_ATTN, self.idx, vx)
317 cross_attention_mask = video.cross_attention_mask
318 cross_output_mask = video.cross_output_mask
319 if self.idx >= int(self.num_layers * 0.7):
320 if video.late_cross_attention_mask is not None:
321 cross_attention_mask = video.late_cross_attention_mask
322 if video.late_cross_output_mask is not None:
323 cross_output_mask = video.late_cross_output_mask
324 cross_output_mask = cross_output_mask if cross_output_mask is not None else 1.0
325 vx = vx + (
326 self.audio_to_video_attn(
327 vx_scaled,
328 context=ax_scaled,
329 mask=cross_attention_mask,
330 pe=video.cross_positional_embeddings,
331 k_pe=audio.cross_positional_embeddings,
332 )
333 * gate_out_a2v
334 * a2v_mask
335 * cross_output_mask
336 )
337 del gate_out_a2v, a2v_mask, vx_scaled, ax_scaled, cross_output_mask
338
339 if run_v2a and not perturbations.all_in_batch(PerturbationType.SKIP_V2A_CROSS_ATTN, self.idx):
340 scale_ca_audio_v2a, shift_ca_audio_v2a, gate_out_v2a = self.get_av_ca_ada_values(
341 self.scale_shift_table_a2v_ca_audio,
342 ax.shape[0],
343 audio.cross_scale_shift_timestep,
344 audio.cross_gate_timestep,
345 slice(2, 4),
346 )
347 ax_scaled = ax_norm3 * (1 + scale_ca_audio_v2a) + shift_ca_audio_v2a
348 del scale_ca_audio_v2a, shift_ca_audio_v2a
349 scale_ca_video_v2a, shift_ca_video_v2a, _ = self.get_av_ca_ada_values(
350 self.scale_shift_table_a2v_ca_video,
351 vx.shape[0],
352 video.cross_scale_shift_timestep,
353 video.cross_gate_timestep,
354 slice(2, 4),
355 )
356 vx_scaled = vx_norm3 * (1 + scale_ca_video_v2a) + shift_ca_video_v2a
357 del scale_ca_video_v2a, shift_ca_video_v2a
358 v2a_mask = perturbations.mask_like(PerturbationType.SKIP_V2A_CROSS_ATTN, self.idx, ax)
359 cross_attention_mask = audio.cross_attention_mask
360 cross_output_mask = audio.cross_output_mask
361 if self.idx >= int(self.num_layers * 0.7):
362 if audio.late_cross_attention_mask is not None:
363 cross_attention_mask = audio.late_cross_attention_mask
364 if audio.late_cross_output_mask is not None:
365 cross_output_mask = audio.late_cross_output_mask
366 cross_output_mask = cross_output_mask if cross_output_mask is not None else 1.0
367 v2a_update = (
368 self.video_to_audio_attn(
369 ax_scaled,
370 context=vx_scaled,
371 mask=cross_attention_mask,
372 pe=audio.cross_positional_embeddings,
373 k_pe=video.cross_positional_embeddings,
374 )
375 * gate_out_v2a
376 * v2a_mask
377 * cross_output_mask
378 )
379 v2a_grad_scale = float(getattr(audio, "v2a_grad_scale", 1.0))
380 if v2a_grad_scale != 1.0 and torch.is_grad_enabled():
381 v2a_update = v2a_update.detach() + v2a_grad_scale * (v2a_update - v2a_update.detach())
382 ax = ax + v2a_update
383 del gate_out_v2a, v2a_mask, ax_scaled, vx_scaled, cross_output_mask, v2a_update
384
385 del vx_norm3, ax_norm3
386
387 if run_vx:
388 vshift_mlp, vscale_mlp, vgate_mlp = self.get_ada_values(
389 self.scale_shift_table, vx.shape[0], video.timesteps, slice(3, 6)
390 )
391 vx_scaled = rms_norm(vx, eps=self.norm_eps) * (1 + vscale_mlp) + vshift_mlp
392 vx = vx + self.ff(vx_scaled) * vgate_mlp
393
394 del vshift_mlp, vscale_mlp, vgate_mlp, vx_scaled
395
396 if run_ax:
397 ashift_mlp, ascale_mlp, agate_mlp = self.get_ada_values(
398 self.audio_scale_shift_table, ax.shape[0], audio.timesteps, slice(3, 6)
399 )
400 ax_scaled = rms_norm(ax, eps=self.norm_eps) * (1 + ascale_mlp) + ashift_mlp
401 ax = ax + self.audio_ff(ax_scaled) * agate_mlp
402
403 del ashift_mlp, ascale_mlp, agate_mlp, ax_scaled
404
405 return replace(video, x=vx) if video is not None else None, replace(audio, x=ax) if audio is not None else None
406
407
408 def apply_cross_attention_adaln(
409 x: torch.Tensor,
410 context: torch.Tensor,
411 attn: AttentionCallable,
412 q_shift: torch.Tensor,
413 q_scale: torch.Tensor,
414 q_gate: torch.Tensor,
415 prompt_scale_shift_table: torch.Tensor,
416 prompt_timestep: torch.Tensor,
417 context_mask: torch.Tensor | None = None,
418 norm_eps: float = 1e-6,
419 ) -> torch.Tensor:
420 batch_size = x.shape[0]
421 shift_kv, scale_kv = (
422 prompt_scale_shift_table[None, None].to(device=x.device, dtype=x.dtype)
423 + prompt_timestep.reshape(batch_size, prompt_timestep.shape[1], 2, -1)
424 ).unbind(dim=2)
425 attn_input = rms_norm(x, eps=norm_eps) * (1 + q_scale) + q_shift
426 encoder_hidden_states = context * (1 + scale_kv) + shift_kv
427 return attn(attn_input, context=encoder_hidden_states, mask=context_mask) * q_gate
428
428 lines PYTHON