| import json |
| import math |
| import os |
|
|
| import torch |
| from einops import rearrange |
| from loguru import logger |
| from safetensors.torch import load_file |
| from safetensors.torch import load_file as load_sft |
| from torch import Tensor |
|
|
| from .mage_flow import MageFlow, MageFlowParams |
|
|
|
|
| def get_noise( |
| num_samples: int, |
| channel: int, |
| height: int, |
| width: int, |
| device: torch.device, |
| dtype: torch.dtype, |
| seed: int, |
| ): |
| |
| return torch.randn( |
| num_samples, |
| channel, |
| math.ceil(height / 16), |
| math.ceil(width / 16), |
| device=device, |
| dtype=dtype, |
| generator=torch.Generator(device=device).manual_seed(seed), |
| ) |
|
|
|
|
| def unpack(x: Tensor, height: int, width: int) -> Tensor: |
| |
| return rearrange( |
| x, |
| "b (h w) c -> b c h w", |
| h=math.ceil(height / 16), |
| w=math.ceil(width / 16), |
| ) |
|
|
|
|
| PROMPT_TEMPLATE = { |
| "default": {"template": "{}", "start_idx": 0}, |
| "default-nonthinking": {"template": "{}<think>\n\n</think>\n\n", "start_idx": 0}, |
| "mage-flow": { |
| "template": ( |
| "<|im_start|>system\nDescribe the image by detailing the color, shape, size, texture, quantity, " |
| "text, spatial relationships of the objects and background:" |
| "<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n" |
| ), |
| "start_idx": 34, |
| }, |
| "mage-flow-edit": { |
| "template": ( |
| "<|im_start|>system\nDescribe the key features of the input image (color, shape, size, texture," |
| " objects, background), then explain how the user's text instruction should alter or modify the image. " |
| "Generate a new image that meets the user's requirements while maintaining consistency with the original " |
| "input where appropriate.<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n" |
| ), |
| "start_idx": 64, |
| }, |
| } |
|
|
|
|
| def print_load_warning(missing: list[str], unexpected: list[str]) -> None: |
| if len(missing) > 0 and len(unexpected) > 0: |
| logger.warning(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing)) |
| logger.warning("\n" + "-" * 79 + "\n") |
| logger.warning(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected)) |
| elif len(missing) > 0: |
| logger.warning(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing)) |
| elif len(unexpected) > 0: |
| logger.warning(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected)) |
|
|
|
|
| def correct_model_weight(state_dict): |
| result = {} |
| for key in state_dict.keys(): |
| if "_orig_mod." in key: |
| result[key[10:]] = state_dict[key] |
| else: |
| result[key] = state_dict[key] |
| return result |
|
|
|
|
| def load_hf_style_weight(pretrain_path, device): |
| index_path = os.path.join(pretrain_path, "diffusion_pytorch_model.safetensors.index.json") |
|
|
| with open(index_path) as f: |
| index = json.load(f) |
|
|
| weight_map = index["weight_map"] |
|
|
| sd = {} |
| loaded_shards = set() |
|
|
| for shard_file in weight_map.values(): |
| if shard_file in loaded_shards: |
| continue |
| shard_path = os.path.join(pretrain_path, shard_file) |
| shard_sd = load_file(shard_path, device="cpu") |
| sd.update(shard_sd) |
| loaded_shards.add(shard_file) |
|
|
| return sd |
|
|
|
|
| def load_model_weight(model, pretrain_path, device="cpu"): |
| if os.path.exists(pretrain_path): |
| logger.info(f"Loading checkpoint from {pretrain_path}") |
| try: |
| if pretrain_path.endswith("safetensors"): |
| sd = load_sft(pretrain_path, device="cpu") |
| elif os.path.exists(os.path.join(pretrain_path, "diffusion_pytorch_model.safetensors.index.json")): |
| sd = load_hf_style_weight(pretrain_path, device) |
| else: |
| sd = torch.load(pretrain_path, map_location="cpu") |
|
|
| sd = correct_model_weight(sd) |
| sd = optionally_expand_state_dict(model, sd) |
| missing, unexpected = model.load_state_dict(sd, strict=False, assign=True) |
| print_load_warning(missing, unexpected) |
| return True |
| except Exception as e: |
| logger.info(f"CANNOT Load {pretrain_path}, because {e}") |
| return False |
| return False |
|
|
|
|
| def load_model(dit_structure: dict, pretrain_path: str | None = None): |
| logger.info("Init DiT model") |
|
|
| |
| |
| |
| params = MageFlowParams(**dit_structure) |
| |
| |
| |
| model = MageFlow(params) |
|
|
| |
| if pretrain_path is not None: |
| load_model_weight(model, pretrain_path, device="cpu") |
| |
| |
| |
| |
| |
| |
| return model |
|
|
|
|
| def optionally_expand_state_dict(model: torch.nn.Module, state_dict: dict) -> dict: |
| """ |
| Optionally expand the state dict to match the model's parameters shapes. |
| """ |
| for name, param in model.named_parameters(): |
| if name in state_dict: |
| if state_dict[name].shape != param.shape: |
| logger.info( |
| f"Expanding '{name}' with shape {state_dict[name].shape} to model parameter with shape " |
| f"{param.shape}." |
| ) |
| |
| expanded_state_dict_weight = torch.zeros_like(param, device=state_dict[name].device) |
| slices = tuple(slice(0, dim) for dim in state_dict[name].shape) |
| expanded_state_dict_weight[slices] = state_dict[name] |
| state_dict[name] = expanded_state_dict_weight |
|
|
| return state_dict |
|
|