Download src/models/dit_blocks.py from kaupane/Inko: direct link, hf CLI and curl.
- Browser
- Download file 33.2 kB
-
https://huggingface.co/spaces/kaupane/Inko/resolve/main/src/models/dit_blocks.py
- Command line
-
hf download hf://spaces/kaupane/Inko/src/models/dit_blocks.py
-
curl -L -o dit_blocks.py https://huggingface.co/spaces/kaupane/Inko/resolve/main/src/models/dit_blocks.py
33.2 kB
| """Attention, positional features and native Inko transformer blocks.""" | |
| from typing import Tuple, Optional, Sequence | |
| import functools | |
| import math | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from .varlen_attention import packed_attention | |
| # Optional SDPA backend restriction for the masked (padded-text) attention | |
| # path; None keeps the library default. | |
| _SDPA_BACKENDS = None | |
| class RMSNorm(nn.RMSNorm): | |
| """Fuse Ascend normalization while retaining FP32 arithmetic and gains.""" | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if ( | |
| x.device.type != "npu" | |
| or self.weight is None | |
| or len(self.normalized_shape) != 1 | |
| or x.dtype not in (torch.float16, torch.bfloat16, torch.float32) | |
| ): | |
| return super().forward(x) | |
| import torch_npu | |
| # PyTorch's mixed-dtype RMSNorm promotes the input to FP32 and applies | |
| # the learned gain before casting back. Casting the gain to BF16 just | |
| # to use a fused kernel would change that computation. | |
| eps = self.eps if self.eps is not None else torch.finfo(x.dtype).eps | |
| y, _ = torch_npu.npu_rms_norm( | |
| x.float(), self.weight.float(), epsilon=eps | |
| ) | |
| return y.to(x.dtype) | |
| def sdpa_with_pad_mask( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| attn_mask: Optional[torch.Tensor], | |
| ) -> torch.Tensor: | |
| """SDPA with an optional bool key-padding mask of shape [B, 1, 1, S_k]. | |
| A bool mask forces PyTorch off the flash path; the math fallback then | |
| materializes B*H*S^2 attention weights per layer (saved for backward), | |
| which at seq ~2K is tens of GiB per layer and OOMs long-caption batches. | |
| We convert the mask to an additive bias in q.dtype | |
| and prefer the memory-efficient kernel (never materializes S^2 scores); | |
| mask=None keeps the default (flash) path untouched. | |
| """ | |
| if attn_mask is None: | |
| return F.scaled_dot_product_attention(q, k, v, dropout_p=0.0) | |
| return sdpa_with_bias(q, k, v, pad_bias_from_mask(attn_mask, q.dtype)) | |
| def set_sdpa_backends(names: Optional[Sequence[str]]) -> None: | |
| """Restrict SDPA backends for masked (padded-text) attention. | |
| ``None`` keeps the library default (memory-efficient first). Otherwise | |
| masked attention is limited to the named ``SDPBackend`` members, with the | |
| math backend appended as a fallback. Training keeps the default: on a | |
| single 4090 at 256p (micro-batch 16, 128-192 text tokens) cuDNN attention | |
| is ~30% slower than memory-efficient on the forward+backward training | |
| step, despite a faster forward-only kernel with an additive pad bias. | |
| """ | |
| global _SDPA_BACKENDS | |
| if names is None: | |
| _SDPA_BACKENDS = None | |
| return | |
| from torch.nn.attention import SDPBackend | |
| resolved = [] | |
| for name in names: | |
| member = getattr(SDPBackend, name.strip().upper(), None) | |
| if member is None: | |
| raise ValueError(f"unknown SDPA backend {name!r}") | |
| resolved.append(member) | |
| _SDPA_BACKENDS = tuple(resolved) | |
| def _sdpa_backends(): | |
| if _SDPA_BACKENDS is not None: | |
| return list(_SDPA_BACKENDS) | |
| from torch.nn.attention import SDPBackend | |
| return [SDPBackend.EFFICIENT_ATTENTION, SDPBackend.MATH] | |
| def sdpa_with_bias( | |
| q: torch.Tensor, | |
| k: torch.Tensor, | |
| v: torch.Tensor, | |
| bias: torch.Tensor, | |
| ) -> torch.Tensor: | |
| """SDPA with a precomputed additive bias of shape [B, 1, 1, S_k].""" | |
| if q.device.type == "npu": | |
| # torch_npu's F.sdpa dispatch with a float additive bias materializes | |
| # per-head S^2 bias + P matrix per layer (measured: +2.8 GiB at | |
| # B8/S2304), which OOMs long-caption batches. npu_fusion_attention with | |
| # a bool (B,1,S,S) drop-mask (True = drop) is true flash attention: | |
| # +0.06 GiB saved for backward, maxdiff 2e-3 vs the float-bias path at | |
| # S=2304 bf16. Two gotchas, both measured: the scale argument defaults | |
| # to 1.0 (NOT 1/sqrt(D)) so it must be passed explicitly, and the mask | |
| # must be full (B,1,S,S) - aclnn rejects broadcastable (B,1,1,S). | |
| import torch_npu | |
| b, h, s_q, d = q.shape | |
| s_k = k.shape[2] | |
| # bias == -inf: identical to torch.isneginf for float tensors, but | |
| # aten.eq.Scalar has a torchair GE converter while aten.isneginf does | |
| # not (ascend-ta-bench3 NotImplementedError). | |
| drop = bias == float("-inf") | |
| mask = drop.expand(b, 1, s_q, s_k).contiguous() | |
| return torch_npu.npu_fusion_attention( | |
| q, k, v, h, "BNSD", atten_mask=mask, keep_prob=1.0, scale=d**-0.5 | |
| )[0] | |
| from torch.nn.attention import sdpa_kernel | |
| backends = _sdpa_backends() | |
| if q.device.type != "cuda": | |
| from torch.nn.attention import SDPBackend | |
| backends = [SDPBackend.MATH] | |
| with sdpa_kernel(backends): | |
| return F.scaled_dot_product_attention(q, k, v, attn_mask=bias, dropout_p=0.0) | |
| def pad_bias_from_mask(attn_mask: torch.Tensor, dtype: torch.dtype) -> torch.Tensor: | |
| """Convert a bool keep-mask [B, 1, 1, S_k] into an additive SDPA bias.""" | |
| bias = torch.zeros(attn_mask.shape, dtype=dtype, device=attn_mask.device) | |
| return bias.masked_fill(~attn_mask, float("-inf")) | |
| class TimestepEmbeddings(nn.Module): | |
| """Sinusoidal features; scaling applies here, never to the flow path.""" | |
| def __init__(self, hidden_size: int): | |
| super().__init__() | |
| self.hidden_size = hidden_size | |
| self.register_buffer("factor", torch.tensor(1000, dtype=torch.int64)) | |
| def _load_from_state_dict( | |
| self, | |
| state_dict, | |
| prefix, | |
| local_metadata, | |
| strict, | |
| missing_keys, | |
| unexpected_keys, | |
| error_msgs, | |
| ): | |
| factor = state_dict.get(prefix + "factor") | |
| if factor is None or factor.item() != 1000: | |
| error_msgs.append(f"{prefix}expected timestep feature factor 1000") | |
| super()._load_from_state_dict( | |
| state_dict, | |
| prefix, | |
| local_metadata, | |
| strict, | |
| missing_keys, | |
| unexpected_keys, | |
| error_msgs, | |
| ) | |
| def forward(self, timesteps: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Args: | |
| timesteps: (B,) | |
| Returns: | |
| embedding: (B, N) | |
| """ | |
| half = self.hidden_size // 2 | |
| exponent = ( | |
| -math.log(10000) | |
| * torch.arange( | |
| start=0, end=half, dtype=torch.float32, device=timesteps.device | |
| ) | |
| / half | |
| ) | |
| emb = torch.exp(exponent) | |
| times = timesteps.float() * 1000 | |
| emb = times.unsqueeze(1) * emb.unsqueeze(0) | |
| return torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1) | |
| def apply_rotary_emb(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Apply rotary embeddings | |
| Args: | |
| x: Input tensor [B, S, H, D] | |
| freqs_cis: Complex frequency tensor [S, D/2] | |
| Returns: | |
| Tensor with rotary embeddings applied [B, S, H, D] | |
| """ | |
| # Reshape x to [B, S, H, D/2, 2] and view as complex | |
| x_complex = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) | |
| # Reshape for broadcasting: [S, D/2] -> [1, S, 1, D/2] | |
| freqs_cis = freqs_cis.unsqueeze(0).unsqueeze(2) | |
| # Apply rotation | |
| x_rotated = x_complex * freqs_cis | |
| # View as real and flatten the last two dimensions | |
| x_out = torch.view_as_real(x_rotated).flatten(3) | |
| return x_out.type_as(x) | |
| def apply_rotary_emb_real(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor: | |
| """Equivalent FP32 rotation expressed without complex multiplication. | |
| This lets Inductor fuse surrounding casts/layout operations. Compiled | |
| rounding may differ; the complex implementation remains the default. | |
| """ | |
| pairs = x.float().reshape(*x.shape[:-1], -1, 2) | |
| real, imag = pairs.unbind(-1) | |
| cosine, sine = torch.view_as_real(freqs_cis)[None, :, None].unbind(-1) | |
| rotated = torch.stack( | |
| (real * cosine - imag * sine, real * sine + imag * cosine), dim=-1 | |
| ) | |
| return rotated.flatten(3).type_as(x) | |
| def apply_rotary_emb_realfreq( | |
| x: torch.Tensor, freqs_real: torch.Tensor | |
| ) -> torch.Tensor: | |
| """apply_rotary_emb_real taking an already-real frequency tensor. | |
| freqs_real is [S, D/2, 2] float32 (cos, sin pairs) — the view_as_real of | |
| the complex table, materialized OUTSIDE compiled regions so no | |
| complex-typed tensor crosses the graph boundary (torchair/GE has no | |
| complex dtype support). | |
| """ | |
| pairs = x.float().reshape(*x.shape[:-1], -1, 2) | |
| real, imag = pairs.unbind(-1) | |
| cosine, sine = freqs_real[None, :, None].unbind(-1) | |
| rotated = torch.stack( | |
| (real * cosine - imag * sine, real * sine + imag * cosine), dim=-1 | |
| ) | |
| return rotated.flatten(3).type_as(x) | |
| def set_real_rope(model: nn.Module, enabled: bool) -> None: | |
| """Set an instance-local execution policy before compiling the model. | |
| This is not checkpoint state: loading weights must not override the launch | |
| policy. Other live models and the complex frequency tables are untouched. | |
| """ | |
| if type(enabled) is not bool: | |
| raise ValueError("real RoPE policy must be boolean") | |
| for module in model.modules(): | |
| if isinstance(module, (DoubleStreamAttention, SingleStreamAttention)): | |
| module.real_rope = enabled | |
| class MSRoPE(nn.Module): | |
| """Multimodal Scalable RoPE for 2D images and text""" | |
| def __init__( | |
| self, | |
| theta: int = 10000, | |
| axes_dim: list = [64, 64], | |
| scaling_type: str = "none", | |
| scaling_factor: float = 1.0, | |
| centered: bool = False, | |
| ): | |
| super().__init__() | |
| self.theta = theta | |
| self.axes_dim = axes_dim | |
| self.scaling_type = scaling_type | |
| self.scaling_factor = scaling_factor | |
| # centered: image coords symmetric around 0 ([-(n-1)/2, (n-1)/2]) instead of | |
| # 0-based from the corner; text stays pinned to a fixed diagonal beyond the | |
| # image range in both modes. | |
| self.centered = centered | |
| # Precompute frequency tables for both spatial axes | |
| pos_index = torch.arange(2560) # maximum position index | |
| # Store as real to avoid safetensors issues with complex numbers | |
| self.register_buffer( | |
| "pos_freqs", torch.view_as_real(self._build_frequency_table(pos_index)) | |
| ) | |
| # LRU cache for image frequency computation | |
| self._cache = {} | |
| def rope_params(self, index: torch.Tensor, dim: int) -> torch.Tensor: | |
| """ | |
| Generate RoPE parameters for given dimensions | |
| Args: | |
| index: Position indices [S] | |
| dim: Embedding dimension for this axis | |
| Returns: | |
| Complex frequency tensor [S, dim] | |
| """ | |
| assert dim % 2 == 0 | |
| # Apply scaling | |
| if self.scaling_type == "linear": | |
| index = index / self.scaling_factor | |
| theta = self.theta | |
| elif self.scaling_type == "ntk": | |
| theta = self.theta * (self.scaling_factor ** (dim / (dim - 2))) | |
| else: | |
| theta = self.theta | |
| # Compute frequency components: 1/ theta^(2i/dim) | |
| freqs = torch.outer( | |
| index, | |
| 1.0 / torch.pow(theta, torch.arange(0, dim, 2).to(torch.float32).div(dim)), | |
| ) | |
| # Convert to complex polar form: e^(i*freqs) | |
| return torch.polar(torch.ones_like(freqs), freqs) | |
| def _build_frequency_table(self, pos_index: torch.Tensor) -> torch.Tensor: | |
| """Build frequency table for both height and width axes""" | |
| height_freqs = self.rope_params(pos_index, self.axes_dim[0]) | |
| width_freqs = self.rope_params(pos_index, self.axes_dim[1]) | |
| # Concatenate height and width frequencies: [S, height_dim+width_dim] | |
| return torch.cat([height_freqs, width_freqs], dim=1) | |
| def forward( | |
| self, img_hw: Tuple[int, int], txt_seq_len: int, device | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """ | |
| Compute frequency tensors for image and text sequences | |
| Args: | |
| img_hw: Image dimensions (height, width) | |
| txt_seq_len: Length of text sequence | |
| device: Device for tensor placement | |
| Returns: | |
| Tuple of (image_freqs, text_freqs) tensors | |
| """ | |
| height, width = img_hw | |
| # Check if we need to expand the frequency table | |
| max_needed = max(height, width) + txt_seq_len | |
| current_max = self.pos_freqs.shape[0] | |
| if max_needed > current_max: | |
| # Expand by 2x or to max_needed, whichever is larger | |
| new_max = max(max_needed, current_max * 2) | |
| # print(f"Expanding RoPE frequency table from {current_max} to {new_max}") | |
| pos_index = torch.arange(new_max, device=device) | |
| self.pos_freqs = torch.view_as_real(self._build_frequency_table(pos_index)) | |
| # Ensure frequencies are on correct device | |
| if self.pos_freqs.device != device: | |
| self.pos_freqs = self.pos_freqs.to(device) | |
| # Get cached image frequencies or compute new ones | |
| cache_key = (height, width) | |
| if cache_key in self._cache: | |
| img_freqs = self._cache[cache_key] | |
| else: | |
| img_freqs = self._compute_image_freqs(height, width) | |
| self._cache[cache_key] = img_freqs | |
| img_freqs = img_freqs.to(device) | |
| # Text frequencies start after maximum image position | |
| max_img_pos = max(height, width) | |
| # View as complex for slicing (must be float32 — view_as_complex doesn't support bf16) | |
| pos_freqs_complex = torch.view_as_complex(self.pos_freqs.float()) | |
| txt_freqs = pos_freqs_complex[ | |
| max_img_pos : max_img_pos + txt_seq_len, : | |
| ] # placing text tokens on a diagonal in the 2D position space | |
| return img_freqs, txt_freqs | |
| def prepare_freqs( | |
| self, img_hw: Tuple[int, int], txt_seq_len: int, device | |
| ) -> torch.Tensor: | |
| """Concatenated [S_img + S_txt, D/2] complex freqs, reusable across blocks. | |
| Identical to applying ``forward()`` and concatenating the two halves; | |
| hoisting it out of the block loop removes one table lookup and two | |
| concatenations per layer. | |
| """ | |
| img_freqs, txt_freqs = self.forward(img_hw, txt_seq_len, device) | |
| return torch.cat([img_freqs, txt_freqs], dim=0) | |
| def _compute_image_freqs(self, height: int, width: int) -> torch.Tensor: | |
| """ | |
| Compute frequency tensor for 2D image grid | |
| Args: | |
| height: Height of image | |
| width: Width of image | |
| Returns: | |
| Frequency tensor [height*width, total_dim] (complex) | |
| """ | |
| # Split precomputed frequencies by axis | |
| # pos_freqs is [S, D, 2] (real) — cast to float32 for view_as_complex | |
| h_dim, w_dim = self.axes_dim | |
| if self.centered: | |
| # Symmetric (possibly half-integer) coordinates around the origin. | |
| h_idx = torch.arange(height, dtype=torch.float32) - (height - 1) / 2.0 | |
| w_idx = torch.arange(width, dtype=torch.float32) - (width - 1) / 2.0 | |
| # rope_params returns complex [N, dim//2]; store as real pairs | |
| h_freqs = torch.view_as_real(self.rope_params(h_idx, h_dim)) | |
| w_freqs = torch.view_as_real(self.rope_params(w_idx, w_dim)) | |
| else: | |
| pos_freqs = self.pos_freqs.float() | |
| h_freqs, w_freqs = pos_freqs.split([h_dim // 2, w_dim // 2], dim=1) | |
| # Select frequencies for the current height and width | |
| h_freqs = h_freqs[:height, :] # [H, h_dim//2, 2] | |
| w_freqs = w_freqs[:width, :] # [W, w_dim//2, 2] | |
| # Broadcast to create the grid | |
| # Expand to [H, W, dim, 2] | |
| h_freqs_grid = h_freqs.unsqueeze(1).expand( | |
| height, width, -1, -1 | |
| ) # [height, width, h_dim//2, 2] | |
| w_freqs_grid = w_freqs.unsqueeze(0).expand( | |
| height, width, -1, -1 | |
| ) # [height, width, w_dim//2, 2] | |
| # Concatenate | |
| freqs = torch.cat( | |
| [h_freqs_grid, w_freqs_grid], dim=2 | |
| ) # [height, width, (h_dim+w_dim)//2, 2] | |
| # Flatten spatial dimensions | |
| freqs = freqs.flatten(0, 1) # [height*width, (h_dim+w_dim)//2, 2] | |
| # Convert back to complex | |
| freqs = torch.view_as_complex(freqs) | |
| return freqs | |
| class GatedFeedForward(nn.Module): | |
| def __init__(self, dim: int, hidden_dim: int, dropout: float = 0.0): | |
| super().__init__() | |
| self.up_proj = nn.Linear(dim, hidden_dim * 2) | |
| self.down_proj = nn.Linear(hidden_dim, dim) | |
| self.dropout = nn.Dropout(dropout) | |
| self.dropout_out = nn.Dropout(dropout) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| x = self.up_proj(x) | |
| if x.device.type == "npu": | |
| import torch_npu | |
| x = torch_npu.npu_swiglu(x, dim=-1) | |
| else: | |
| x_gated, x_linear = x.chunk(2, dim=-1) | |
| x = F.silu(x_gated) * x_linear | |
| x = self.dropout(x) | |
| x = self.down_proj(x) | |
| return self.dropout_out(x) | |
| def modulate(x: torch.Tensor, shift: torch.Tensor, scale: torch.Tensor) -> torch.Tensor: | |
| return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) | |
| class DoubleStreamAttention(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int, | |
| qkv_bias: bool = True, | |
| rope_theta: int = 10000, | |
| rope_axes_dim: list = [64, 64], | |
| rope_scaling_type: str = "none", | |
| rope_scaling_factor: float = 1.0, | |
| rope_centered: bool = False, | |
| ): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.head_dim = dim // num_heads | |
| self.scale = self.head_dim**-0.5 | |
| self.real_rope = False | |
| self.qkv_img = nn.Linear(dim, dim * 3, bias=qkv_bias) | |
| self.qkv_txt = nn.Linear(dim, dim * 3, bias=qkv_bias) | |
| self.q_norm_img = RMSNorm(self.head_dim, eps=1e-6) | |
| self.k_norm_img = RMSNorm(self.head_dim, eps=1e-6) | |
| self.q_norm_txt = RMSNorm(self.head_dim, eps=1e-6) | |
| self.k_norm_txt = RMSNorm(self.head_dim, eps=1e-6) | |
| self.rope = MSRoPE( | |
| theta=rope_theta, | |
| axes_dim=rope_axes_dim, | |
| scaling_type=rope_scaling_type, | |
| scaling_factor=rope_scaling_factor, | |
| centered=rope_centered, | |
| ) | |
| self.proj_img = nn.Linear(dim, dim) | |
| self.proj_txt = nn.Linear(dim, dim) | |
| def forward( | |
| self, | |
| img_tokens: torch.Tensor, | |
| txt_tokens: torch.Tensor, | |
| img_hw: Tuple[int, int], | |
| txt_seq_len: int, | |
| txt_attention_mask: Optional[torch.Tensor] = None, | |
| attn_metadata=None, | |
| rope_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| B, S_img, C = img_tokens.shape | |
| _, S_txt, _ = txt_tokens.shape | |
| # Image QKV | |
| qkv_img = ( | |
| self.qkv_img(img_tokens) | |
| .reshape(B, S_img, 3, self.num_heads, self.head_dim) | |
| .permute(2, 0, 3, 1, 4) | |
| ) | |
| q_img, k_img, v_img = qkv_img.unbind(0) # [B, H, S, D] | |
| # Text QKV | |
| qkv_txt = ( | |
| self.qkv_txt(txt_tokens) | |
| .reshape(B, S_txt, 3, self.num_heads, self.head_dim) | |
| .permute(2, 0, 3, 1, 4) | |
| ) | |
| q_txt, k_txt, v_txt = qkv_txt.unbind(0) # [B, H, S, D] | |
| # QK Norm | |
| q_img = self.q_norm_img(q_img) | |
| k_img = self.k_norm_img(k_img) | |
| q_txt = self.q_norm_txt(q_txt) | |
| k_txt = self.k_norm_txt(k_txt) | |
| # RoPE | |
| img_freqs, txt_freqs = ( | |
| self.rope(img_hw, txt_seq_len, img_tokens.device) | |
| if rope_freqs is None | |
| else rope_freqs | |
| ) | |
| # Apply RoPE (need to transpose to [B, S, H, D] for apply_rotary_emb) | |
| rotate = apply_rotary_emb_real if self.real_rope else apply_rotary_emb | |
| q_img = rotate(q_img.transpose(1, 2), img_freqs).transpose(1, 2) | |
| k_img = rotate(k_img.transpose(1, 2), img_freqs).transpose(1, 2) | |
| q_txt = rotate(q_txt.transpose(1, 2), txt_freqs).transpose(1, 2) | |
| k_txt = rotate(k_txt.transpose(1, 2), txt_freqs).transpose(1, 2) | |
| # Concat | |
| q = torch.cat([q_img, q_txt], dim=2) | |
| k = torch.cat([k_img, k_txt], dim=2) | |
| v = torch.cat([v_img, v_txt], dim=2) | |
| # Prepare attention mask | |
| attn_mask = None | |
| if txt_attention_mask is not None and attn_metadata is None: | |
| # Image tokens always have attention | |
| img_mask = torch.ones( | |
| B, | |
| S_img, | |
| device=txt_attention_mask.device, | |
| dtype=txt_attention_mask.dtype, | |
| ) | |
| full_mask = torch.cat([img_mask, txt_attention_mask], dim=1) | |
| # Convert to attention mask format [B, 1, 1, S_total] | |
| # Mask: 1 (valid) -> True (attend), 0 (padding) -> False (ignore) | |
| attn_mask = (full_mask > 0).unsqueeze(1).unsqueeze(2) | |
| attn_mask = attn_mask.to(dtype=torch.bool) | |
| # Attention with mask | |
| x = ( | |
| packed_attention(q, k, v, attn_metadata) | |
| if attn_metadata is not None | |
| else sdpa_with_pad_mask(q, k, v, attn_mask) | |
| ) | |
| # Split | |
| x_img = x[:, :, :S_img, :] | |
| x_txt = x[:, :, S_img:, :] | |
| # Reshape and Project | |
| x_img = x_img.transpose(1, 2).reshape(B, S_img, C) | |
| x_txt = x_txt.transpose(1, 2).reshape(B, S_txt, C) | |
| x_img = self.proj_img(x_img) | |
| x_txt = self.proj_txt(x_txt) | |
| return x_img, x_txt | |
| class DoubleStreamDiTBlock(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int, | |
| c_dim: int, | |
| mlp_ratio: float = 4.0, | |
| rope_theta: int = 10000, | |
| rope_axes_dim: list = [64, 64], | |
| ): | |
| super().__init__() | |
| self.dim = dim | |
| self.num_heads = num_heads | |
| self.mlp_ratio = mlp_ratio | |
| # Normalize branch outputs before learned residual gates. | |
| self.norm_msa_img_out = RMSNorm(dim, eps=1e-6) | |
| self.norm_msa_txt_out = RMSNorm(dim, eps=1e-6) | |
| self.norm_mlp_img_out = RMSNorm(dim, eps=1e-6) | |
| self.norm_mlp_txt_out = RMSNorm(dim, eps=1e-6) | |
| # Modulation | |
| self.modulation_img = nn.Sequential( | |
| nn.SiLU(), nn.Linear(c_dim, 6 * dim, bias=True) | |
| ) | |
| self.modulation_txt = nn.Sequential( | |
| nn.SiLU(), nn.Linear(c_dim, 6 * dim, bias=True) | |
| ) | |
| # Attention | |
| self.norm1_img = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| self.norm1_txt = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| self.attn = DoubleStreamAttention( | |
| dim, | |
| num_heads, | |
| True, | |
| rope_theta, | |
| rope_axes_dim, | |
| rope_scaling_type="none", | |
| rope_scaling_factor=1.0, | |
| rope_centered=True, | |
| ) | |
| # MLP | |
| self.norm2_img = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| self.norm2_txt = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| mlp_hidden_dim = int(dim * mlp_ratio) | |
| self.mlp_img = GatedFeedForward(dim, mlp_hidden_dim) | |
| self.mlp_txt = GatedFeedForward(dim, mlp_hidden_dim) | |
| def forward( | |
| self, | |
| img_tokens: torch.Tensor, | |
| txt_tokens: torch.Tensor, | |
| c: torch.Tensor, | |
| img_hw: Tuple[int, int], | |
| txt_seq_len: int, | |
| txt_attention_mask: Optional[torch.Tensor] = None, | |
| attn_metadata=None, | |
| rope_freqs: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| # Modulation | |
| # c: [B, c_dim] | |
| ( | |
| shift_msa_img, | |
| scale_msa_img, | |
| gate_msa_img, | |
| shift_mlp_img, | |
| scale_mlp_img, | |
| gate_mlp_img, | |
| ) = self.modulation_img(c).chunk(6, dim=1) | |
| ( | |
| shift_msa_txt, | |
| scale_msa_txt, | |
| gate_msa_txt, | |
| shift_mlp_txt, | |
| scale_mlp_txt, | |
| gate_mlp_txt, | |
| ) = self.modulation_txt(c).chunk(6, dim=1) | |
| # 1. Attention Block | |
| img_norm = modulate(self.norm1_img(img_tokens), shift_msa_img, scale_msa_img) | |
| txt_norm = modulate(self.norm1_txt(txt_tokens), shift_msa_txt, scale_msa_txt) | |
| img_attn, txt_attn = self.attn( | |
| img_norm, | |
| txt_norm, | |
| img_hw, | |
| txt_seq_len, | |
| txt_attention_mask, | |
| attn_metadata=attn_metadata, | |
| rope_freqs=rope_freqs, | |
| ) | |
| img_attn = self.norm_msa_img_out(img_attn) | |
| txt_attn = self.norm_msa_txt_out(txt_attn) | |
| img_tokens = img_tokens + gate_msa_img.unsqueeze(1) * img_attn | |
| txt_tokens = txt_tokens + gate_msa_txt.unsqueeze(1) * txt_attn | |
| # 2. MLP Block | |
| img_norm = modulate(self.norm2_img(img_tokens), shift_mlp_img, scale_mlp_img) | |
| txt_norm = modulate(self.norm2_txt(txt_tokens), shift_mlp_txt, scale_mlp_txt) | |
| mlp_img_out = self.mlp_img(img_norm) | |
| mlp_txt_out = self.mlp_txt(txt_norm) | |
| mlp_img_out = self.norm_mlp_img_out(mlp_img_out) | |
| mlp_txt_out = self.norm_mlp_txt_out(mlp_txt_out) | |
| img_tokens = img_tokens + gate_mlp_img.unsqueeze(1) * mlp_img_out | |
| txt_tokens = txt_tokens + gate_mlp_txt.unsqueeze(1) * mlp_txt_out | |
| return img_tokens, txt_tokens | |
| def initialize_weights(self): | |
| # AdaLN-zero: Initialize modulation MLP to zero | |
| nn.init.constant_(self.modulation_img[-1].weight, 0) | |
| nn.init.constant_(self.modulation_img[-1].bias, 0) | |
| nn.init.constant_(self.modulation_txt[-1].weight, 0) | |
| nn.init.constant_(self.modulation_txt[-1].bias, 0) | |
| class SingleStreamAttention(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int, | |
| qkv_bias: bool = True, | |
| rope_theta: int = 10000, | |
| rope_axes_dim: list = [64, 64], | |
| rope_scaling_type: str = "none", | |
| rope_scaling_factor: float = 1.0, | |
| rope_centered: bool = False, | |
| ): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.head_dim = dim // num_heads | |
| self.scale = self.head_dim**-0.5 | |
| self.real_rope = False | |
| self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) | |
| self.q_norm = RMSNorm(self.head_dim, eps=1e-6) | |
| self.k_norm = RMSNorm(self.head_dim, eps=1e-6) | |
| self.rope = MSRoPE( | |
| theta=rope_theta, | |
| axes_dim=rope_axes_dim, | |
| scaling_type=rope_scaling_type, | |
| scaling_factor=rope_scaling_factor, | |
| centered=rope_centered, | |
| ) | |
| self.proj = nn.Linear(dim, dim) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| img_hw: Tuple[int, int], | |
| txt_seq_len: int, | |
| txt_attention_mask: Optional[torch.Tensor] = None, | |
| rope_freqs: Optional[torch.Tensor] = None, | |
| attn_bias: Optional[torch.Tensor] = None, | |
| attn_metadata=None, | |
| ) -> torch.Tensor: | |
| B, S, C = x.shape | |
| qkv = ( | |
| self.qkv(x) | |
| .reshape(B, S, 3, self.num_heads, self.head_dim) | |
| .permute(2, 0, 3, 1, 4) | |
| ) | |
| q, k, v = qkv.unbind(0) | |
| q = self.q_norm(q) | |
| k = self.k_norm(k) | |
| if rope_freqs is not None: | |
| # Frequencies already cover [img, txt] in that order, so RoPE is a | |
| # single elementwise pass over the concatenated sequence. Under | |
| # the real-rope policy the hoisted table arrives in real | |
| # [S, D/2, 2] layout (converted once at model level, outside any | |
| # compiled block); otherwise it is complex. | |
| rotate = apply_rotary_emb_realfreq if self.real_rope else apply_rotary_emb | |
| q = rotate(q.transpose(1, 2), rope_freqs).transpose(1, 2) | |
| k = rotate(k.transpose(1, 2), rope_freqs).transpose(1, 2) | |
| else: | |
| rotate = apply_rotary_emb_real if self.real_rope else apply_rotary_emb | |
| # Need to apply different RoPE to img and txt parts | |
| # Assuming x is [img, txt] | |
| S_img = img_hw[0] * img_hw[1] | |
| q_img = q[:, :, :S_img, :] | |
| k_img = k[:, :, :S_img, :] | |
| q_txt = q[:, :, S_img:, :] | |
| k_txt = k[:, :, S_img:, :] | |
| img_freqs, txt_freqs = self.rope(img_hw, txt_seq_len, x.device) | |
| q_img = rotate(q_img.transpose(1, 2), img_freqs).transpose(1, 2) | |
| k_img = rotate(k_img.transpose(1, 2), img_freqs).transpose(1, 2) | |
| q_txt = rotate(q_txt.transpose(1, 2), txt_freqs).transpose(1, 2) | |
| k_txt = rotate(k_txt.transpose(1, 2), txt_freqs).transpose(1, 2) | |
| q = torch.cat([q_img, q_txt], dim=2) | |
| k = torch.cat([k_img, k_txt], dim=2) | |
| if attn_metadata is not None: | |
| x = packed_attention(q, k, v, attn_metadata) | |
| return self.proj(x.transpose(1, 2).reshape(B, S, C)) | |
| if attn_bias is not None: | |
| x = sdpa_with_bias(q, k, v, attn_bias) | |
| x = x.transpose(1, 2).reshape(B, S, C) | |
| return self.proj(x) | |
| # Prepare attention mask | |
| attn_mask = None | |
| if txt_attention_mask is not None: | |
| # Image tokens always have attention | |
| img_mask = torch.ones( | |
| B, | |
| S_img, | |
| device=txt_attention_mask.device, | |
| dtype=txt_attention_mask.dtype, | |
| ) | |
| full_mask = torch.cat([img_mask, txt_attention_mask], dim=1) | |
| # Convert to attention mask format [B, 1, 1, S] | |
| # Mask : 1 (valid) -> True (attend), 0 (padding) -> False (ignore) | |
| attn_mask = (full_mask > 0).unsqueeze(1).unsqueeze(2) | |
| attn_mask = attn_mask.to(dtype=torch.bool) | |
| # Attention with mask | |
| x = sdpa_with_pad_mask(q, k, v, attn_mask) | |
| x = x.transpose(1, 2).reshape(B, S, C) | |
| x = self.proj(x) | |
| return x | |
| class SingleStreamDiTBlock(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_heads: int, | |
| c_dim: int, | |
| mlp_ratio: float = 4.0, | |
| rope_theta: int = 10000, | |
| rope_axes_dim: list = [64, 64], | |
| ): | |
| super().__init__() | |
| self.dim = dim | |
| self.num_heads = num_heads | |
| self.mlp_ratio = mlp_ratio | |
| # Modulation | |
| self.modulation = nn.Sequential(nn.SiLU(), nn.Linear(c_dim, 3 * dim, bias=True)) | |
| # Normalize branch outputs before the residual add. Learned gates and | |
| # affine norm gains remain outside this control of branch amplitude. | |
| self.norm_msa_out = RMSNorm(dim, eps=1e-6) | |
| self.norm_mlp_out = RMSNorm(dim, eps=1e-6) | |
| self.norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| self.attn = SingleStreamAttention( | |
| dim, | |
| num_heads, | |
| True, | |
| rope_theta, | |
| rope_axes_dim, | |
| rope_scaling_type="none", | |
| rope_scaling_factor=1.0, | |
| rope_centered=True, | |
| ) | |
| # MLP | |
| self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6) | |
| mlp_hidden_dim = int(dim * mlp_ratio) | |
| self.mlp = GatedFeedForward(dim, mlp_hidden_dim) | |
| def forward( | |
| self, | |
| img_tokens: torch.Tensor, | |
| txt_tokens: torch.Tensor, | |
| c: torch.Tensor, | |
| img_hw: Tuple[int, int], | |
| txt_seq_len: int, | |
| txt_attention_mask: Optional[torch.Tensor] = None, | |
| rope_freqs: Optional[torch.Tensor] = None, | |
| attn_bias: Optional[torch.Tensor] = None, | |
| attn_metadata=None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| # Modulation | |
| # c: [B, c_dim] | |
| params = self.modulation(c) | |
| shift, scale, gate = params.chunk(3, dim=1) | |
| shift_msa = shift_mlp = shift | |
| scale_msa = scale_mlp = scale | |
| gate_msa = gate_mlp = gate | |
| # Concatenate for shared MSA/MLP processing | |
| S_img = img_tokens.shape[1] | |
| S_txt = txt_tokens.shape[1] | |
| x = torch.cat([img_tokens, txt_tokens], dim=1) | |
| # 1. Attention Block | |
| x_norm = modulate(self.norm1(x), shift_msa, scale_msa) | |
| x_attn = self.attn( | |
| x_norm, | |
| img_hw, | |
| txt_seq_len, | |
| txt_attention_mask, | |
| rope_freqs=rope_freqs, | |
| attn_bias=attn_bias, | |
| attn_metadata=attn_metadata, | |
| ) | |
| x_attn = self.norm_msa_out(x_attn) | |
| x = x + gate_msa.unsqueeze(1) * x_attn | |
| # 2. MLP Block | |
| x_norm = modulate(self.norm2(x), shift_mlp, scale_mlp) | |
| mlp_out = self.mlp(x_norm) | |
| mlp_out = self.norm_mlp_out(mlp_out) | |
| x = x + gate_mlp.unsqueeze(1) * mlp_out | |
| img_tokens, txt_tokens = x.split([S_img, S_txt], dim=1) | |
| return img_tokens, txt_tokens | |
| def initialize_weights(self): | |
| # AdaLN-zero: Initialize modulation MLP to zero | |
| nn.init.constant_(self.modulation[-1].weight, 0) | |
| nn.init.constant_(self.modulation[-1].bias, 0) | |