ajh-code commited on
Commit
9deda87
·
verified ·
1 Parent(s): 21ee278

Add vendor/mage_flow/models/modules/mage_layers.py

Browse files
vendor/mage_flow/models/modules/mage_layers.py ADDED
@@ -0,0 +1,733 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Any
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from diffusers.models.attention import FeedForward
8
+ from diffusers.models.embeddings import TimestepEmbedding
9
+ from diffusers.models.normalization import RMSNorm
10
+ from ._attn_backend import flash_attn_varlen_func
11
+ from torch import Tensor
12
+ from torch._dynamo import allow_in_graph as maybe_allow_in_graph
13
+
14
+
15
+ def apply_rotary_emb_mageflow(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
16
+ """Apply complex rotary embeddings to `x` ([B, S, H, D]) using `freqs_cis`
17
+ (the MageFlowEmbedRope 2D multi-scale RoPE, adjacent-pair complex convention)."""
18
+ x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
19
+ freqs_cis = freqs_cis.unsqueeze(1)
20
+ x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(-2)
21
+ return x_out.type_as(x)
22
+
23
+
24
+ def get_timestep_embedding(
25
+ timesteps: torch.Tensor,
26
+ embedding_dim: int,
27
+ flip_sin_to_cos: bool = False,
28
+ downscale_freq_shift: float = 1,
29
+ scale: float = 1,
30
+ max_period: int = 10000,
31
+ ) -> torch.Tensor:
32
+ """Sinusoidal timestep embeddings (DDPM convention).
33
+
34
+ NOTE: kept vendored (not diffusers') because the frequency table is
35
+ downcast to ``timesteps.dtype`` (bf16) here — the model was trained with
36
+ this exact bf16 rounding, so diffusers' fp32 variant produces a slightly
37
+ different embedding and degrades outputs.
38
+ """
39
+ assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array"
40
+
41
+ half_dim = embedding_dim // 2
42
+ exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device)
43
+ exponent = exponent / (half_dim - downscale_freq_shift)
44
+
45
+ emb = torch.exp(exponent).to(timesteps.dtype)
46
+ emb = timesteps[:, None].float() * emb[None, :]
47
+
48
+ # scale embeddings
49
+ emb = scale * emb
50
+
51
+ # concat sine and cosine embeddings
52
+ emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
53
+
54
+ # flip sine and cosine embeddings
55
+ if flip_sin_to_cos:
56
+ emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1)
57
+
58
+ # zero pad
59
+ if embedding_dim % 2 == 1:
60
+ emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
61
+ return emb
62
+
63
+
64
+ class Timesteps(nn.Module):
65
+ def __init__(
66
+ self,
67
+ num_channels: int,
68
+ flip_sin_to_cos: bool,
69
+ downscale_freq_shift: float,
70
+ scale: int = 1,
71
+ ):
72
+ super().__init__()
73
+ self.num_channels = num_channels
74
+ self.flip_sin_to_cos = flip_sin_to_cos
75
+ self.downscale_freq_shift = downscale_freq_shift
76
+ self.scale = scale
77
+
78
+ def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
79
+ t_emb = get_timestep_embedding(
80
+ timesteps,
81
+ self.num_channels,
82
+ flip_sin_to_cos=self.flip_sin_to_cos,
83
+ downscale_freq_shift=self.downscale_freq_shift,
84
+ scale=self.scale,
85
+ )
86
+ return t_emb
87
+
88
+
89
+ class MageFlowTimestepProjEmbeddings(nn.Module):
90
+ def __init__(self, embedding_dim):
91
+ super().__init__()
92
+
93
+ self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=0, scale=1000)
94
+ self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)
95
+
96
+ def forward(self, timestep, hidden_states):
97
+ timesteps_proj = self.time_proj(timestep)
98
+ timesteps_emb = self.timestep_embedder(timesteps_proj.to(dtype=hidden_states.dtype)) # (N, D)
99
+
100
+ conditioning = timesteps_emb
101
+
102
+ return conditioning
103
+
104
+
105
+ class MageFlowEmbedRope(nn.Module):
106
+ def __init__(self, theta: int, axes_dim: list[int], scale_rope=False):
107
+ super().__init__()
108
+ self.theta = theta
109
+ self.axes_dim = axes_dim
110
+ pos_index = torch.arange(4096)
111
+ neg_index = torch.arange(4096).flip(0) * -1 - 1
112
+ self.pos_freqs = torch.cat(
113
+ [
114
+ self.rope_params(pos_index, self.axes_dim[0], self.theta),
115
+ self.rope_params(pos_index, self.axes_dim[1], self.theta),
116
+ self.rope_params(pos_index, self.axes_dim[2], self.theta),
117
+ ],
118
+ dim=1,
119
+ )
120
+ self.neg_freqs = torch.cat(
121
+ [
122
+ self.rope_params(neg_index, self.axes_dim[0], self.theta),
123
+ self.rope_params(neg_index, self.axes_dim[1], self.theta),
124
+ self.rope_params(neg_index, self.axes_dim[2], self.theta),
125
+ ],
126
+ dim=1,
127
+ )
128
+
129
+ # DO NOT USING REGISTER BUFFER HERE, IT WILL CAUSE COMPLEX NUMBERS LOSE ITS IMAGINARY PART
130
+ self.scale_rope = scale_rope
131
+ self.video_freq_cache = {}
132
+
133
+ def rope_params(self, index, dim, theta=10000):
134
+ """
135
+ Args:
136
+ index: [0, 1, 2, 3] 1D Tensor representing the position index of the token
137
+ """
138
+ assert dim % 2 == 0
139
+ freqs = torch.outer(
140
+ index,
141
+ 1.0 / torch.pow(theta, torch.arange(0, dim, 2).to(torch.float32).div(dim)),
142
+ )
143
+ freqs = torch.polar(torch.ones_like(freqs), freqs)
144
+ return freqs
145
+
146
+ def forward(
147
+ self,
148
+ video_fhw: tuple[int, int, int] | list[tuple[int, int, int]],
149
+ device: torch.device,
150
+ max_img_len: int = None,
151
+ ) -> torch.Tensor:
152
+ """Compute the vision RoPE frequencies (`vid_freqs`) for the packed image
153
+ tokens. Text tokens are NOT rotated, so no text RoPE is computed.
154
+
155
+ Args:
156
+ video_fhw (`Tuple[int, int, int]` or `List[Tuple[int, int, int]]`):
157
+ A list of 3 integers [frame, height, width] representing the shape of the video.
158
+ device: (`torch.device`):
159
+ The device on which to perform the RoPE computation.
160
+ """
161
+ if self.pos_freqs.device != device:
162
+ self.pos_freqs = self.pos_freqs.to(device)
163
+ self.neg_freqs = self.neg_freqs.to(device)
164
+
165
+ if isinstance(video_fhw, list):
166
+ video_fhw = video_fhw[0]
167
+ if not isinstance(video_fhw, list):
168
+ video_fhw = [video_fhw]
169
+
170
+ vid_freqs = []
171
+ for idx, fhw in enumerate(video_fhw):
172
+ frame, height, width = fhw
173
+ # RoPE frequencies are cached manually
174
+ key = (frame, height, width, idx)
175
+ if key not in self.video_freq_cache:
176
+ self.video_freq_cache[key] = self._compute_video_freqs(frame, height, width, idx)
177
+ vid_freqs.append(self.video_freq_cache[key].to(device))
178
+
179
+ vid_freqs = torch.cat(vid_freqs, dim=0)
180
+
181
+ if max_img_len is not None and vid_freqs.shape[0] < max_img_len:
182
+ pad_len = max_img_len - vid_freqs.shape[0]
183
+ vid_freqs = torch.nn.functional.pad(vid_freqs, (0, 0, 0, pad_len))
184
+
185
+ return vid_freqs
186
+
187
+ def _compute_video_freqs(self, frame: int, height: int, width: int, idx: int = 0) -> torch.Tensor:
188
+ seq_lens = frame * height * width
189
+ freqs_pos = self.pos_freqs.split([x // 2 for x in self.axes_dim], dim=1)
190
+ freqs_neg = self.neg_freqs.split([x // 2 for x in self.axes_dim], dim=1)
191
+
192
+ freqs_frame = freqs_pos[0][idx : idx + frame].view(frame, 1, 1, -1).expand(frame, height, width, -1)
193
+ if self.scale_rope:
194
+ freqs_height = torch.cat(
195
+ [freqs_neg[1][-(height - height // 2) :], freqs_pos[1][: height // 2]],
196
+ dim=0,
197
+ )
198
+ freqs_height = freqs_height.view(1, height, 1, -1).expand(frame, height, width, -1)
199
+ freqs_width = torch.cat(
200
+ [freqs_neg[2][-(width - width // 2) :], freqs_pos[2][: width // 2]],
201
+ dim=0,
202
+ )
203
+ freqs_width = freqs_width.view(1, 1, width, -1).expand(frame, height, width, -1)
204
+ else:
205
+ freqs_height = freqs_pos[1][:height].view(1, height, 1, -1).expand(frame, height, width, -1)
206
+ freqs_width = freqs_pos[2][:width].view(1, 1, width, -1).expand(frame, height, width, -1)
207
+
208
+ freqs = torch.cat([freqs_frame, freqs_height, freqs_width], dim=-1).reshape(seq_lens, -1)
209
+ return freqs.clone().contiguous()
210
+
211
+
212
+ class Attention(nn.Module):
213
+ def __init__(
214
+ self,
215
+ query_dim: int,
216
+ cross_attention_dim: int | None = None,
217
+ heads: int = 8,
218
+ kv_heads: int | None = None,
219
+ dim_head: int = 64,
220
+ dropout: float = 0.0,
221
+ bias: bool = False,
222
+ scale_qk: bool = True,
223
+ added_kv_proj_dim: int | None = None,
224
+ added_proj_bias: bool | None = True,
225
+ out_bias: bool = True,
226
+ eps: float = 1e-5,
227
+ processor=None,
228
+ out_dim: int = None,
229
+ out_context_dim: int = None,
230
+ elementwise_affine: bool = True,
231
+ ):
232
+ super().__init__()
233
+ # logger.info(f"processor: {processor}")
234
+
235
+ self.inner_dim = out_dim if out_dim is not None else dim_head * heads
236
+ self.inner_kv_dim = self.inner_dim if kv_heads is None else dim_head * kv_heads
237
+ self.query_dim = query_dim
238
+ self.use_bias = bias
239
+ self.is_cross_attention = cross_attention_dim is not None
240
+ self.cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim
241
+ self.fused_projections = False
242
+ self.out_dim = out_dim if out_dim is not None else query_dim
243
+ self.out_context_dim = out_context_dim if out_context_dim is not None else query_dim
244
+
245
+ self.scale_qk = scale_qk
246
+ self.scale = dim_head**-0.5 if self.scale_qk else 1.0
247
+
248
+ self.heads = out_dim // dim_head if out_dim is not None else heads
249
+ # for slice_size > 0 the attention score computation
250
+ # is split across the batch axis to save memory
251
+ # You can set_slice_size with `set_attention_slice`
252
+ self.sliceable_head_dim = heads
253
+
254
+ self.added_kv_proj_dim = added_kv_proj_dim
255
+
256
+ # qk_norm is always "rms_norm" for MageFlow.
257
+ self.norm_q = RMSNorm(dim_head, eps=eps, elementwise_affine=elementwise_affine)
258
+ self.norm_k = RMSNorm(dim_head, eps=eps, elementwise_affine=elementwise_affine)
259
+
260
+ self.to_q = nn.Linear(query_dim, self.inner_dim, bias=bias)
261
+ self.to_k = nn.Linear(self.cross_attention_dim, self.inner_kv_dim, bias=bias)
262
+ self.to_v = nn.Linear(self.cross_attention_dim, self.inner_kv_dim, bias=bias)
263
+
264
+ self.added_proj_bias = added_proj_bias
265
+ if self.added_kv_proj_dim is not None:
266
+ self.add_k_proj = nn.Linear(added_kv_proj_dim, self.inner_kv_dim, bias=added_proj_bias)
267
+ self.add_v_proj = nn.Linear(added_kv_proj_dim, self.inner_kv_dim, bias=added_proj_bias)
268
+ self.add_q_proj = nn.Linear(added_kv_proj_dim, self.inner_dim, bias=added_proj_bias)
269
+ self.norm_added_q = RMSNorm(dim_head, eps=eps)
270
+ self.norm_added_k = RMSNorm(dim_head, eps=eps)
271
+ else:
272
+ self.add_q_proj = None
273
+ self.add_k_proj = None
274
+ self.add_v_proj = None
275
+ self.norm_added_q = None
276
+ self.norm_added_k = None
277
+
278
+ self.to_out = nn.ModuleList([])
279
+ self.to_out.append(nn.Linear(self.inner_dim, self.out_dim, bias=out_bias))
280
+ self.to_out.append(nn.Dropout(dropout))
281
+
282
+ self.to_add_out = nn.Linear(self.inner_dim, self.out_context_dim, bias=out_bias)
283
+
284
+ self.set_processor(processor)
285
+
286
+ def set_processor(self, processor) -> None:
287
+ self.processor = processor
288
+
289
+ def get_processor(self):
290
+ return self.processor
291
+
292
+ def forward(
293
+ self,
294
+ hidden_states: torch.Tensor,
295
+ attention_mask: torch.Tensor | None = None,
296
+ txt_cu_lens: torch.Tensor | None = None,
297
+ img_cu_lens: torch.Tensor | None = None,
298
+ # ms_pe: tuple[torch.FloatTensor, torch.FloatTensor] | None = None,
299
+ # pe: torch.FloatTensor | None = None,
300
+ # freqs_cos: torch.Tensor | None = None,
301
+ # freqs_sin: torch.Tensor | None = None,
302
+ image_rotary_emb: torch.Tensor | None = None,
303
+ **attention_kwargs,
304
+ ) -> torch.Tensor:
305
+ r"""
306
+ The forward method of the `Attention` class.
307
+
308
+ Args:
309
+ hidden_states (`torch.Tensor`):
310
+ The hidden states of the query.
311
+ encoder_hidden_states (`torch.Tensor`, *optional*):
312
+ The hidden states of the encoder.
313
+ attention_mask (`torch.Tensor`, *optional*):
314
+ The attention mask to use. If `None`, no mask is applied.
315
+ **attention_kwargs:
316
+ Additional keyword arguments to pass along to the attention.
317
+
318
+ Returns:
319
+ `torch.Tensor`: The output of the attention layer.
320
+ """
321
+ # The `Attention` class can call different attention processors / attention functions
322
+ # here we simply pass along all tensors to the selected processor class
323
+ # For standard processors that are defined here, `**attention_kwargs` is empty
324
+
325
+ return self.processor(
326
+ self,
327
+ hidden_states,
328
+ attention_mask=attention_mask,
329
+ txt_cu_lens=txt_cu_lens,
330
+ img_cu_lens=img_cu_lens,
331
+ image_rotary_emb=image_rotary_emb,
332
+ **attention_kwargs,
333
+ )
334
+
335
+
336
+ class MageDoubleStreamAttnProcessor:
337
+ """
338
+ Attention processor for the Mage double-stream architecture, matching DoubleStreamLayerMegatron logic. This processor
339
+ implements joint attention computation where text and image streams are processed together.
340
+ """
341
+
342
+ _attention_backend = None
343
+ _parallel_config = None
344
+
345
+ def __init__(self):
346
+ if not hasattr(F, "scaled_dot_product_attention"):
347
+ raise ImportError(
348
+ "MageDoubleStreamAttnProcessor requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
349
+ )
350
+
351
+ def __call__(
352
+ self,
353
+ attn: Attention,
354
+ hidden_states: torch.FloatTensor, # Image stream
355
+ img_cu_lens: torch.LongTensor,
356
+ attention_mask: torch.FloatTensor | None = None,
357
+ encoder_hidden_states: torch.FloatTensor = None, # Text stream
358
+ txt_cu_lens: torch.LongTensor = None,
359
+ image_rotary_emb: torch.Tensor | None = None,
360
+ **kwargs,
361
+ ) -> torch.FloatTensor:
362
+ if encoder_hidden_states is None:
363
+ raise ValueError("MageDoubleStreamAttnProcessor requires encoder_hidden_states (text stream)")
364
+
365
+ # seq_txt = encoder_hidden_states.shape[1]
366
+
367
+ # logger.info(f"hidden_states: {hidden_states.shape}")
368
+ # logger.info(f"encoder_hidden_states: {encoder_hidden_states.shape}")
369
+
370
+ # Compute QKV for image stream (sample projections). Experimental
371
+ # runtimes may provide one fused projection; the ordinary checkpoint
372
+ # path continues to use the three standard Linear modules.
373
+ if getattr(attn, "to_qkv", None) is not None:
374
+ img_query, img_key, img_value = attn.to_qkv(hidden_states).chunk(3, dim=-1)
375
+ else:
376
+ img_query = attn.to_q(hidden_states)
377
+ img_key = attn.to_k(hidden_states)
378
+ img_value = attn.to_v(hidden_states)
379
+
380
+ # Compute QKV for text stream (context projections).
381
+ if getattr(attn, "add_qkv_proj", None) is not None:
382
+ txt_query, txt_key, txt_value = attn.add_qkv_proj(encoder_hidden_states).chunk(3, dim=-1)
383
+ else:
384
+ txt_query = attn.add_q_proj(encoder_hidden_states)
385
+ txt_key = attn.add_k_proj(encoder_hidden_states)
386
+ txt_value = attn.add_v_proj(encoder_hidden_states)
387
+
388
+ # Reshape for multi-head attention
389
+ img_query = img_query.unflatten(-1, (attn.heads, -1))
390
+ img_key = img_key.unflatten(-1, (attn.heads, -1))
391
+ img_value = img_value.unflatten(-1, (attn.heads, -1))
392
+
393
+ txt_query = txt_query.unflatten(-1, (attn.heads, -1))
394
+ txt_key = txt_key.unflatten(-1, (attn.heads, -1))
395
+ txt_value = txt_value.unflatten(-1, (attn.heads, -1))
396
+
397
+ # logger.info(
398
+ # f"img_query shape: {img_query.shape}, img_key shape: {img_key.shape}, img_value shape: {img_value.shape}"
399
+ # )
400
+ # logger.info(
401
+ # f"txt_query shape: {txt_query.shape}, txt_key shape: {txt_key.shape}, txt_value shape: {txt_value.shape}"
402
+ # )
403
+
404
+ if img_query.ndim == 4:
405
+ img_query = img_query.flatten(0, 1)
406
+ img_key = img_key.flatten(0, 1)
407
+ img_value = img_value.flatten(0, 1)
408
+
409
+ if txt_query.ndim == 4:
410
+ txt_query = txt_query.flatten(0, 1)
411
+ txt_key = txt_key.flatten(0, 1)
412
+ txt_value = txt_value.flatten(0, 1)
413
+
414
+ # Apply QK normalization
415
+ if attn.norm_q is not None:
416
+ img_query = attn.norm_q(img_query)
417
+ if attn.norm_k is not None:
418
+ img_key = attn.norm_k(img_key)
419
+ if attn.norm_added_q is not None:
420
+ txt_query = attn.norm_added_q(txt_query)
421
+ if attn.norm_added_k is not None:
422
+ txt_key = attn.norm_added_k(txt_key)
423
+
424
+ # logger.info(f"txt_query shape: {txt_query.shape}, txt_key shape: {txt_key.shape}")
425
+ # logger.info(f"freqs_cos shape: {freqs_cos.shape}, freqs_sin shape: {freqs_sin.shape}")
426
+
427
+ # Apply 2D multi-scale RoPE (MageFlowEmbedRope) to image tokens
428
+ img_freqs = image_rotary_emb
429
+ img_query = apply_rotary_emb_mageflow(img_query, img_freqs)
430
+ img_key = apply_rotary_emb_mageflow(img_key, img_freqs)
431
+ # Concatenate for joint attention
432
+ # Order: [text, image]
433
+ # joint_query = torch.cat([txt_query, img_query], dim=1)
434
+ # joint_key = torch.cat([txt_key, img_key], dim=1)
435
+ # joint_value = torch.cat([txt_value, img_value], dim=1)
436
+
437
+ # Calculate lengths
438
+ img_lens = img_cu_lens[1:] - img_cu_lens[:-1]
439
+ txt_lens = txt_cu_lens[1:] - txt_cu_lens[:-1]
440
+
441
+ # Calculate joint cu_seqlens
442
+ joint_lens = txt_lens + img_lens
443
+ joint_cu_lens = torch.cat(
444
+ [
445
+ torch.zeros(1, dtype=torch.int32, device=joint_lens.device),
446
+ torch.cumsum(joint_lens, dim=0, dtype=torch.int32),
447
+ ],
448
+ dim=0,
449
+ )
450
+
451
+ # logger.info(f"txt_lens: {txt_lens}, img_lens: {img_lens}")
452
+ # logger.info(f"joint_lens: {joint_lens}, joint_cu_lens: {joint_cu_lens}")
453
+
454
+ device = joint_lens.device
455
+ batch_size = len(txt_lens)
456
+ sample_indices = torch.arange(batch_size, device=device)
457
+
458
+ txt_sample_ids = torch.repeat_interleave(sample_indices, txt_lens)
459
+ img_sample_ids = torch.repeat_interleave(sample_indices, img_lens)
460
+
461
+ txt_intra_pos = torch.arange(txt_query.shape[0], device=device) - txt_cu_lens[txt_sample_ids]
462
+ img_intra_pos = torch.arange(img_query.shape[0], device=device) - img_cu_lens[img_sample_ids]
463
+
464
+ txt_dest_indices = joint_cu_lens[txt_sample_ids] + txt_intra_pos
465
+ img_dest_indices = joint_cu_lens[img_sample_ids] + txt_lens[img_sample_ids] + img_intra_pos
466
+
467
+ total_tokens = joint_cu_lens[-1]
468
+ joint_query = torch.empty((total_tokens, *txt_query.shape[1:]), dtype=txt_query.dtype, device=device)
469
+ joint_key = torch.empty((total_tokens, *txt_key.shape[1:]), dtype=txt_key.dtype, device=device)
470
+ joint_value = torch.empty((total_tokens, *txt_value.shape[1:]), dtype=txt_value.dtype, device=device)
471
+
472
+ # logger.info(f"joint_query shape: {joint_query.shape}")
473
+ # logger.info(f"joint_key shape: {joint_key.shape}")
474
+ # logger.info(f"joint_value shape: {joint_value.shape}")
475
+ # logger.info(f"txt_dest_indices shape: {txt_dest_indices.shape}")
476
+ # logger.info(f"img_dest_indices shape: {img_dest_indices.shape}")
477
+
478
+ joint_query[txt_dest_indices] = txt_query
479
+ joint_query[img_dest_indices] = img_query
480
+
481
+ joint_key[txt_dest_indices] = txt_key
482
+ joint_key[img_dest_indices] = img_key
483
+
484
+ joint_value[txt_dest_indices] = txt_value
485
+ joint_value[img_dest_indices] = img_value
486
+
487
+ max_seqlen = joint_lens.max().item()
488
+ joint_attn_output = flash_attn_varlen_func(
489
+ joint_query,
490
+ joint_key,
491
+ joint_value,
492
+ cu_seqlens_q=joint_cu_lens,
493
+ cu_seqlens_k=joint_cu_lens,
494
+ max_seqlen_q=max_seqlen,
495
+ max_seqlen_k=max_seqlen,
496
+ dropout_p=0.0,
497
+ softmax_scale=None,
498
+ causal=False,
499
+ )
500
+
501
+ txt_attn_output = joint_attn_output[txt_dest_indices]
502
+ img_attn_output = joint_attn_output[img_dest_indices]
503
+
504
+ img_attn_output = img_attn_output.flatten(1, 2) # (N, H, D) -> (N, H*D)
505
+ img_attn_output = img_attn_output.to(joint_query.dtype)
506
+
507
+ txt_attn_output = txt_attn_output.flatten(1, 2) # (N, H, D) -> (N, H*D)
508
+ txt_attn_output = txt_attn_output.to(joint_query.dtype)
509
+
510
+ img_attn_output = attn.to_out[0](img_attn_output)
511
+ if len(attn.to_out) > 1:
512
+ img_attn_output = attn.to_out[1](img_attn_output) # dropout
513
+
514
+ txt_attn_output = attn.to_add_out(txt_attn_output)
515
+ txt_attn_output = txt_attn_output.view(
516
+ encoder_hidden_states.shape[0], encoder_hidden_states.shape[1], txt_attn_output.shape[-1]
517
+ )
518
+
519
+ return img_attn_output, txt_attn_output
520
+
521
+
522
+ @maybe_allow_in_graph
523
+ class MageFlowTransformerBlock(nn.Module):
524
+ def __init__(
525
+ self,
526
+ dim: int,
527
+ num_attention_heads: int,
528
+ attention_head_dim: int,
529
+ eps: float = 1e-6,
530
+ ):
531
+ super().__init__()
532
+
533
+ self.dim = dim
534
+ self.num_attention_heads = num_attention_heads
535
+ self.attention_head_dim = attention_head_dim
536
+
537
+ # Image processing modules
538
+ self.img_mod = nn.Sequential(
539
+ nn.SiLU(),
540
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
541
+ )
542
+ self.img_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
543
+ self.attn = Attention(
544
+ query_dim=dim,
545
+ cross_attention_dim=None, # Enable cross attention for joint computation
546
+ added_kv_proj_dim=dim, # Enable added KV projections for text stream
547
+ dim_head=attention_head_dim,
548
+ heads=num_attention_heads,
549
+ out_dim=dim,
550
+ bias=True,
551
+ processor=MageDoubleStreamAttnProcessor(),
552
+ eps=eps,
553
+ )
554
+ self.img_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
555
+ self.img_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
556
+
557
+ # Text processing modules
558
+ self.txt_mod = nn.Sequential(
559
+ nn.SiLU(),
560
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
561
+ )
562
+ self.txt_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
563
+ # Text doesn't need separate attention - it's handled by img_attn joint computation
564
+ self.txt_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
565
+ self.txt_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
566
+
567
+ def _modulate(self, x, mod_params, cu_lens=None, seq_lens=None):
568
+ """Apply modulation to input tensor"""
569
+ shift, scale, gate = mod_params.chunk(3, dim=-1)
570
+ if cu_lens is not None:
571
+ assert x.shape[0] == 1, "x must be of shape (1, *) when cu_lens is not None"
572
+ x_flattened = x.view(-1, x.shape[-1])
573
+ lengths = cu_lens[1:] - cu_lens[:-1]
574
+ shift_t = shift.repeat_interleave(lengths, dim=0)
575
+ scale_t = scale.repeat_interleave(lengths, dim=0)
576
+ gate_t = gate.repeat_interleave(lengths, dim=0)
577
+
578
+ x_flattened = x_flattened * (1 + scale_t) + shift_t
579
+ x = x_flattened.view(x.shape)
580
+ return x, gate_t
581
+ else:
582
+ return x * (1 + scale) + shift, gate
583
+
584
+ def forward(
585
+ self,
586
+ hidden_states: torch.Tensor,
587
+ encoder_hidden_states: torch.Tensor,
588
+ # encoder_hidden_states_mask: torch.Tensor,
589
+ temb: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
590
+ image_rotary_emb: torch.Tensor,
591
+ # freqs_cos: torch.Tensor,
592
+ # freqs_sin: torch.Tensor,
593
+ txt_cu_lens: torch.Tensor,
594
+ img_cu_lens: torch.Tensor,
595
+ joint_attention_kwargs: dict[str, Any] | None = None,
596
+ ) -> tuple[torch.Tensor, torch.Tensor]:
597
+ # Get modulation parameters for both streams
598
+ # if isinstance(temb, tuple):
599
+ # temb_img, temb_txt = temb
600
+ # else:
601
+ # temb_img = temb_txt = temb
602
+
603
+ img_mod_params = self.img_mod(temb) # [B, 6*dim]
604
+ txt_mod_params = self.txt_mod(temb) # [B, 6*dim]
605
+
606
+ # logger.info(f"img_mod_params: {img_mod_params.shape}, txt_mod_params: {txt_mod_params.shape}")
607
+
608
+ # if img_cu_lens is not None and txt_cu_lens is not None and hidden_states.ndim == 2:
609
+ # img_lens = img_cu_lens[1:] - img_cu_lens[:-1]
610
+ # txt_lens = txt_cu_lens[1:] - txt_cu_lens[:-1]
611
+ # img_mod_params = img_mod_params.repeat_interleave(img_lens, dim=0)
612
+ # txt_mod_params = txt_mod_params.repeat_interleave(txt_lens, dim=0)
613
+
614
+ # Split modulation parameters for norm1 and norm2
615
+ img_mod1, img_mod2 = img_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
616
+ txt_mod1, txt_mod2 = txt_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
617
+
618
+ # Process image stream - norm1 + modulation
619
+ img_normed = self.img_norm1(hidden_states)
620
+ img_modulated, img_gate1 = self._modulate(img_normed, img_mod1, cu_lens=img_cu_lens)
621
+
622
+ # Process text stream - norm1 + modulation
623
+ txt_normed = self.txt_norm1(encoder_hidden_states)
624
+ txt_modulated, txt_gate1 = self._modulate(txt_normed, txt_mod1, cu_lens=txt_cu_lens)
625
+
626
+ # Use MageDoubleStreamAttnProcessor for joint attention computation
627
+ # This directly implements the DoubleStreamLayerMegatron logic:
628
+ # 1. Computes QKV for both streams
629
+ # 2. Applies QK normalization and RoPE
630
+ # 3. Concatenates and runs joint attention
631
+ # 4. Splits results back to separate streams
632
+ joint_attention_kwargs = joint_attention_kwargs or {}
633
+ # logger.info(f"img_modulated: {img_modulated}")
634
+ # logger.info(f"txt_modulated: {txt_modulated}")
635
+ attn_output = self.attn(
636
+ hidden_states=img_modulated, # Image stream (will be processed as "sample")
637
+ encoder_hidden_states=txt_modulated, # Text stream (will be processed as "context")
638
+ # encoder_hidden_states_mask=encoder_hidden_states_mask,
639
+ image_rotary_emb=image_rotary_emb,
640
+ txt_cu_lens=txt_cu_lens,
641
+ img_cu_lens=img_cu_lens,
642
+ # freqs_cos=freqs_cos,
643
+ # freqs_sin=freqs_sin,
644
+ **joint_attention_kwargs,
645
+ )
646
+ # logger.info(f"attn_output: {attn_output}")
647
+
648
+ # MageDoubleStreamAttnProcessor returns (img_output, txt_output) when encoder_hidden_states is provided
649
+ img_attn_output, txt_attn_output = attn_output
650
+
651
+ # Apply attention gates and add residual (like in Megatron)
652
+ hidden_states = hidden_states + img_gate1 * img_attn_output
653
+ encoder_hidden_states = encoder_hidden_states + txt_gate1 * txt_attn_output
654
+
655
+ # Process image stream - norm2 + MLP
656
+ img_normed2 = self.img_norm2(hidden_states)
657
+ img_modulated2, img_gate2 = self._modulate(img_normed2, img_mod2, cu_lens=img_cu_lens)
658
+ img_mlp_output = self.img_mlp(img_modulated2)
659
+ hidden_states = hidden_states + img_gate2 * img_mlp_output
660
+
661
+ # Process text stream - norm2 + MLP
662
+ txt_normed2 = self.txt_norm2(encoder_hidden_states)
663
+ txt_modulated2, txt_gate2 = self._modulate(txt_normed2, txt_mod2, cu_lens=txt_cu_lens)
664
+ txt_mlp_output = self.txt_mlp(txt_modulated2)
665
+ encoder_hidden_states = encoder_hidden_states + txt_gate2 * txt_mlp_output
666
+
667
+ # Clip to prevent overflow for fp16
668
+ if encoder_hidden_states.dtype == torch.float16:
669
+ encoder_hidden_states = encoder_hidden_states.clip(-65504, 65504)
670
+ if hidden_states.dtype == torch.float16:
671
+ hidden_states = hidden_states.clip(-65504, 65504)
672
+
673
+ return encoder_hidden_states, hidden_states
674
+
675
+
676
+ class AdaLayerNormContinuous(nn.Module):
677
+ r"""
678
+ Adaptive normalization layer with a norm layer (layer_norm or rms_norm).
679
+
680
+ Args:
681
+ embedding_dim (`int`): Embedding dimension to use during projection.
682
+ conditioning_embedding_dim (`int`): Dimension of the input condition.
683
+ elementwise_affine (`bool`, defaults to `True`):
684
+ Boolean flag to denote if affine transformation should be applied.
685
+ eps (`float`, defaults to 1e-5): Epsilon factor.
686
+ bias (`bias`, defaults to `True`): Boolean flag to denote if bias should be use.
687
+ norm_type (`str`, defaults to `"layer_norm"`):
688
+ Normalization layer to use. Values supported: "layer_norm", "rms_norm".
689
+ """
690
+
691
+ def __init__(
692
+ self,
693
+ embedding_dim: int,
694
+ conditioning_embedding_dim: int,
695
+ # NOTE: It is a bit weird that the norm layer can be configured to have scale and shift parameters
696
+ # because the output is immediately scaled and shifted by the projected conditioning embeddings.
697
+ # Note that AdaLayerNorm does not let the norm layer have scale and shift parameters.
698
+ # However, this is how it was implemented in the original code, and it's rather likely you should
699
+ # set `elementwise_affine` to False.
700
+ elementwise_affine=True,
701
+ eps=1e-5,
702
+ bias=True,
703
+ norm_type="layer_norm",
704
+ ):
705
+ super().__init__()
706
+ self.silu = nn.SiLU()
707
+ self.linear = nn.Linear(conditioning_embedding_dim, embedding_dim * 2, bias=bias)
708
+ if norm_type == "layer_norm":
709
+ self.norm = nn.LayerNorm(embedding_dim, eps, elementwise_affine, bias)
710
+ elif norm_type == "rms_norm":
711
+ self.norm = RMSNorm(embedding_dim, eps, elementwise_affine)
712
+ else:
713
+ raise ValueError(f"unknown norm_type {norm_type}")
714
+
715
+ def forward(
716
+ self, x: torch.Tensor, conditioning_embedding: torch.Tensor,
717
+ cu_seqlens: torch.Tensor | None = None, seq_lens: torch.Tensor | None = None,
718
+ ) -> torch.Tensor:
719
+ # convert back to the original dtype in case `conditioning_embedding`` is upcasted to float32 (needed for
720
+ # hunyuanDiT)
721
+ emb = self.linear(self.silu(conditioning_embedding).to(x.dtype))
722
+ if cu_seqlens is None:
723
+ scale, shift = torch.chunk(emb, 2, dim=-1)
724
+ x = self.norm(x) * (1 + scale) + shift
725
+ else:
726
+ sample_lens = cu_seqlens[1:] - cu_seqlens[:-1]
727
+ flattened_x = x.view(-1, x.shape[-1])
728
+ scale, shift = torch.chunk(emb, 2, dim=-1)
729
+ scale_t = torch.repeat_interleave(scale, sample_lens, dim=0)
730
+ shift_t = torch.repeat_interleave(shift, sample_lens, dim=0)
731
+ flattened_x = self.norm(flattened_x) * (1 + scale_t) + shift_t
732
+ x = flattened_x.view(x.shape)
733
+ return x