Spaces:
Running on Zero
Running on Zero
File size: 15,991 Bytes
f8d22a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | from dataclasses import dataclass
from typing import Any
import torch
import torch.nn as nn
from einops import rearrange, repeat
from loguru import logger
from pydantic import BaseModel, Field
from torch import Tensor
from .modules._attn_backend import set_attn_backend
from .modules.mage_layers import (
AdaLayerNormContinuous,
MageFlowEmbedRope,
MageFlowTimestepProjEmbeddings,
MageFlowTransformerBlock,
RMSNorm,
)
from .modules.text_encoder import TextEncoder, qwen3_patch_forward
class ModelConfig(BaseModel):
static_shift: float = Field(
default=6.0,
description="Static shift value for the z-image time-shift schedule (the only "
"supported schedule). Default: 6.0.",
)
vae_path: str = Field(...)
model_structure: dict = Field(default_factory=dict)
txt_enc_path: str = Field(...)
txt_max_length: int = Field(default=4096)
pretrained_model_name_or_path: str | None = Field(default=None)
pretrained_full_model_path: str | None = Field(default=None) # Load full model weights (DiT + txt_enc + vae)
packing: bool = Field(default=False)
vae_sample_posterior: bool = Field(default=True) # Sample (vs mode) from VAE posterior at encode time (Flux2 + CoD)
vae_encoder_only: bool = Field(default=False) # Skip loading VAE decoder to save GPU memory (training only, MageVAE)
compile_vae_encoder: bool = Field(default=False) # torch.compile VAE encoder to reduce CUDA kernel launch overhead
attn_type: str = Field(
default="flash2",
description="Flash-attn backend used by both the DiT (mage_layers) "
"and the HF text encoder (text_encoder). One of: 'flash2' (default) or 'flash4'.",
)
@dataclass
class MageFlowParams:
in_channels: int
out_channels: int
context_in_dim: int
hidden_size: int
num_heads: int
depth: int
axes_dim: list[int]
checkpoint: bool
patch_size: int = 1
class MageFlow(nn.Module):
def __init__(self, params: MageFlowParams):
super().__init__()
self.params = params
self.checkpoint = params.checkpoint
self.in_channels = params.in_channels
self.out_channels = params.out_channels
self.inner_dim = params.hidden_size # num_attention_heads * attention_head_dim
self.axes_dim = params.axes_dim
self.num_attention_heads = params.num_heads
self.attention_head_dim = self.inner_dim // self.num_attention_heads
self.patch_size = params.patch_size
assert sum(self.axes_dim) == self.attention_head_dim
self.pos_embed = MageFlowEmbedRope(theta=10000, axes_dim=self.axes_dim, scale_rope=True)
self.img_in = nn.Linear(self.in_channels, self.inner_dim)
self.txt_norm = RMSNorm(params.context_in_dim, eps=1e-6)
self.txt_in = nn.Linear(params.context_in_dim, self.inner_dim)
self.time_text_embed = MageFlowTimestepProjEmbeddings(embedding_dim=self.inner_dim)
self.transformer_blocks = nn.ModuleList(
[
MageFlowTransformerBlock(
dim=self.inner_dim,
num_attention_heads=self.num_attention_heads,
attention_head_dim=self.attention_head_dim,
)
for _ in range(params.depth)
]
)
self.norm_out = AdaLayerNormContinuous(self.inner_dim, self.inner_dim, elementwise_affine=False, eps=1e-6)
self.proj_out = nn.Linear(self.inner_dim, self.patch_size * self.patch_size * self.out_channels, bias=True)
def forward(
self,
img: Tensor,
txt: Tensor,
timesteps: Tensor,
img_shapes=None,
img_cu_seqlens: Tensor | None = None,
txt_cu_seqlens: Tensor | None = None,
attention_kwargs: dict[str, Any] | None = None,
) -> Tensor:
if img.ndim != 3 or txt.ndim != 3:
raise ValueError("Input img and txt tensors must have 3 dimensions.")
# Prepare vision RoPE (msrope); text tokens are not rotated.
ms_pe = self.pos_embed(img_shapes, device=img.device)
img = self.img_in(img)
txt = self.txt_norm(txt)
timesteps = timesteps.to(img.dtype)
temb = self.time_text_embed(timesteps, img)
txt = self.txt_in(txt)
txt_vec = torch.zeros(txt.shape[0], self.inner_dim, dtype=txt.dtype, device=txt.device)
temb = temb + txt_vec
attention_kwargs = attention_kwargs or {}
for _index_block, block in enumerate(self.transformer_blocks):
if self.training and self.checkpoint:
txt, img = torch.utils.checkpoint.checkpoint(
block,
img, # hidden_states
txt, # encoder_hidden_states
temb, # temb
ms_pe, # image_rotary_emb
txt_cu_seqlens, # txt_cu_lens
img_cu_seqlens, # img_cu_lens
use_reentrant=False,
)
else:
txt, img = block(
hidden_states=img,
encoder_hidden_states=txt,
txt_cu_lens=txt_cu_seqlens,
img_cu_lens=img_cu_seqlens,
temb=temb,
image_rotary_emb=ms_pe,
joint_attention_kwargs=attention_kwargs,
)
# Use only the image part (hidden_states) from the dual-stream blocks
img = self.norm_out(
img,
temb,
cu_seqlens=img_cu_seqlens,
)
img = self.proj_out(img)
return img
class MageFlowModel(nn.Module):
def __init__(self, config: ModelConfig):
super().__init__()
self.config = config
set_attn_backend(getattr(config, "attn_type", "flash2"))
self.patch_text_encoder_forward()
self.vae = self.load_vae()
self.transformer = self.load_transformer()
self.txt_enc = self.load_text_enc()
# Optionally override all components from a full model checkpoint (e.g. ema.pt)
full_path = getattr(self.config, "pretrained_full_model_path", None)
if full_path is not None:
import os
if os.path.exists(full_path):
logger.info(f"Loading full model weights from {full_path}")
sd = torch.load(full_path, map_location="cpu")
# Handle wrapped EMA format: {'ema_state_dict': ..., ...}
if isinstance(sd, dict) and "ema_state_dict" in sd:
sd = sd["ema_state_dict"]
missing, unexpected = self.load_state_dict(sd, strict=False)
if missing:
logger.warning(f"Full model load missing keys ({len(missing)}): {missing[:5]}...")
if unexpected:
logger.warning(f"Full model load unexpected keys ({len(unexpected)}): {unexpected[:5]}...")
logger.info("Full model weights loaded successfully.")
else:
logger.warning(f"pretrained_full_model_path not found: {full_path}")
# Freeze VAE and Text Encoder
self.vae.requires_grad_(False)
# Drop VAE decoder to save GPU memory (training only, decoder unused during training)
if self.config.vae_encoder_only:
from .modules.mage_vae import MageVAE
if isinstance(self.vae, MageVAE):
decoder_params = sum(p.numel() for p in self.vae.decoder_model.parameters()) / 1e6
self.vae.decoder_model = None
elif hasattr(self.vae, "decoder"):
decoder_params = sum(p.numel() for p in self.vae.decoder.parameters()) / 1e6
self.vae.decoder = None
else:
decoder_params = 0
logger.info(f"vae_encoder_only=True: dropped VAE decoder ({decoder_params:.1f}M params) to save memory")
# NOTE: VAE encoder torch.compile() is deferred to
# maybe_compile_vae_encoder(), called after checkpoint load. Reason:
# avoid wasted compile work before load_checkpoint overwrites weights.
# The save-side _unwrap_compiled_submodules guard in DeepSpeedTrainer
# is a belt-and-suspenders defense against any future code that
# re-introduces the function-style ``module = torch.compile(module)``
# pattern (which does pollute state_dict with ``_orig_mod.``).
# Text encoder is always frozen (inference only).
self.txt_enc.requires_grad_(False)
logger.info(
f"{sum([p.numel() for p in self.transformer.parameters() if p.requires_grad]) / 1000000} M parameters"
)
def patch_text_encoder_forward(self):
qwen3_patch_forward()
logger.info("Patched Qwen3-VL text encoder forward methods")
def maybe_compile_vae_encoder(self) -> None:
"""Compile the VAE encoder with torch.compile() to fuse small ops and
reduce CUDA kernel launch overhead.
Uses ``nn.Module.compile()`` (in-place) for the encoder so the module
hierarchy and parameter names are unchanged — ``state_dict()`` keeps
clean keys (no ``_orig_mod.`` prefix), and checkpoints stay
interchangeable with the non-compiled path.
For the MageVAE branch we still assign ``torch.compile(...)`` to a
method (``_encode_moments``); methods aren't ``nn.Module``s so this
does not pollute ``state_dict()``.
Idempotent: safe to call multiple times; already-compiled modules are
detected and skipped.
"""
if not getattr(self.config, "compile_vae_encoder", False):
return
torch.set_float32_matmul_precision("high")
from .modules.mage_vae import MageVAE
if isinstance(self.vae, MageVAE):
fn = self.vae._encode_moments
if hasattr(fn, "_torchdynamo_orig_callable") or hasattr(fn, "_orig_mod"):
return # already compiled
self.vae._encode_moments = torch.compile(fn, dynamic=True)
logger.info("compile_vae_encoder=True: compiled MageVAE._encode_moments")
elif hasattr(self.vae, "encoder"):
if getattr(self.vae.encoder, "_compiled_call_impl", None) is not None:
return # already compiled
self.vae.encoder.compile()
logger.info("compile_vae_encoder=True: compiled VAE encoder (in-place)")
def load_text_enc(self):
return TextEncoder(
model_name=self.config.txt_enc_path,
version=self.config.txt_enc_path,
tokenizer_max_length=self.config.txt_max_length,
torch_dtype=torch.bfloat16,
prompt_template=None,
dit_structure=self.config.model_structure,
use_packed_text_infer=self.config.packing,
attn_type=getattr(self.config, "attn_type", "flash2"),
)
def load_vae(self):
from .modules.mage_vae import MageVAE
return MageVAE(
ckpt_path=self.config.vae_path,
sample_posterior=self.config.vae_sample_posterior,
)
def load_transformer(self):
# Imported lazily to avoid a circular import: ``utils`` imports MageFlow /
# MageFlowParams from this module.
from .utils import load_model
return load_model(
dit_structure=self.config.model_structure,
pretrain_path=self.config.pretrained_model_name_or_path,
)
def compile(self):
self.transformer.compile()
def compute_vae_encodings(
self,
pixel_values: torch.Tensor | list[torch.Tensor],
with_ids: bool = True,
):
if isinstance(pixel_values, list):
# All same resolution → batch encode via the tensor path
if len(pixel_values) > 1 and len({img.shape for img in pixel_values}) == 1:
stacked = torch.stack(pixel_values, dim=0)
result = self.compute_vae_encodings(stacked, with_ids=with_ids)
# Repack from [N, L, C] batch format to [1, N*L, C] packed format
if with_ids:
model_input, img_shapes, img_ids = result
model_input = model_input.reshape(1, -1, model_input.shape[-1])
img_ids = img_ids.reshape(1, -1, img_ids.shape[-1])
return model_input, img_shapes, img_ids
model_input, img_shapes = result
model_input = model_input.reshape(1, -1, model_input.shape[-1])
return model_input, img_shapes
# Packed / variable-size images
model_inputs = []
img_shapes = []
img_ids_list = []
def _append(latents):
_, _, h, w = latents.shape
img_shapes.append([(1, h, w)])
model_inputs.append(rearrange(latents, "b c h w -> b (h w) c").squeeze(0))
if with_ids:
ids = torch.zeros(h, w, 3, device=latents.device)
ids[..., 1] = ids[..., 1] + torch.arange(h, device=latents.device)[:, None]
ids[..., 2] = ids[..., 2] + torch.arange(w, device=latents.device)[None, :]
img_ids_list.append(rearrange(ids, "h w c -> (h w) c"))
# MageVAE encoder is launch-bound on B=1; group same-shape images
# in the pack into one batched encode call.
if len(pixel_values) > 1:
groups: dict[tuple[int, int], list[int]] = {}
for i, img in enumerate(pixel_values):
key = (int(img.shape[-2]), int(img.shape[-1]))
groups.setdefault(key, []).append(i)
latents_per_idx = [None] * len(pixel_values)
for (h, w), idxs in groups.items():
batch = torch.stack([pixel_values[i] for i in idxs], dim=0)
batch = batch.to(memory_format=torch.contiguous_format).float()
batch = batch.to(self.vae.device, dtype=self.vae.dtype)
with torch.no_grad():
lat = self.vae.encode(batch) # [B, 128, H/16, W/16]
for j, i in enumerate(idxs):
latents_per_idx[i] = lat[j:j + 1]
for latents in latents_per_idx:
_append(latents)
else:
for img in pixel_values:
img = img.unsqueeze(0).to(memory_format=torch.contiguous_format).float()
img = img.to(self.vae.device, dtype=self.vae.dtype)
with torch.no_grad():
latents = self.vae.encode(img) # [1, 128, H/16, W/16]
_append(latents)
model_input = torch.cat(model_inputs, dim=0).unsqueeze(0)
if with_ids:
img_ids = torch.cat(img_ids_list, dim=0).unsqueeze(0)
return model_input, img_shapes, img_ids
return model_input, img_shapes
# Tensor (padded batch)
pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()
pixel_values = pixel_values.to(self.vae.device, dtype=self.vae.dtype)
with torch.no_grad():
model_input = self.vae.encode(pixel_values) # [B, 128, H/16, W/16]
bs, c, h, w = model_input.shape
img_shapes = [[(1, h, w)]] * bs
model_input = rearrange(model_input, "b c h w -> b (h w) c")
if with_ids:
img_ids = torch.zeros(h, w, 3, device=model_input.device)
img_ids[..., 1] = img_ids[..., 1] + torch.arange(h, device=model_input.device)[:, None]
img_ids[..., 2] = img_ids[..., 2] + torch.arange(w, device=model_input.device)[None, :]
img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
return model_input, img_shapes, img_ids
return model_input, img_shapes
|