Spaces:
Running on Zero
Running on Zero
| # PixelDiT T2I model — inference subset. | |
| # | |
| # Provides the bare minimum needed by PidDistillModel: net + frozen text | |
| # encoder + caption embedding helper + a flow-matching `timescale` field. | |
| # Training-time machinery (EMA, REPA, flow-matching trainer, training/validation | |
| # steps) has been removed. | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any | |
| import attrs | |
| import torch | |
| import torch.nn as nn | |
| from torch import Tensor | |
| from pid._ext.imaginaire.lazy_config import instantiate as lazy_instantiate | |
| from pid._ext.imaginaire.model import ImaginaireModel | |
| from pid._ext.imaginaire.utils import misc | |
| from pid._src.utils.context_parallel import broadcast as cp_broadcast | |
| from pid._src.utils.context_parallel import robust_broadcast | |
| try: | |
| from megatron.core import parallel_state | |
| except ImportError: | |
| parallel_state = None # CP is opt-in; gracefully degrade when megatron is absent | |
| logger = logging.getLogger(__name__) | |
| class _EMAStubConfig: | |
| """Minimal stub kept so that DCP ModelWrapper.state_dict() can read `config.ema.enabled`.""" | |
| enabled: bool = False | |
| rate: float = 0.1 | |
| iteration_shift: int = 0 | |
| class PixelDiTModelConfig: | |
| net: Any = None | |
| precision: str = "bfloat16" | |
| ema: _EMAStubConfig = attrs.Factory(_EMAStubConfig) | |
| input_data_key: str = "image" | |
| input_caption_key: str = "caption" | |
| text_encoder_name: str = "gemma-2-2b-it" | |
| caption_channels: int = 2304 | |
| y_norm: bool = True | |
| y_norm_scale_factor: float = 0.01 | |
| model_max_length: int = 300 | |
| chi_prompt: list = attrs.Factory(list) | |
| conditioner: Any = None | |
| # Flow matching: only `fm_timescale` is read at inference (network expects | |
| # t * timescale as its scalar timestep input). | |
| fm_timescale: float = 1000.0 | |
| logit_mean: float = 0.0 | |
| logit_std: float = 1.0 | |
| prediction_type: str = "velocity" | |
| shift: float = 4.0 | |
| cfg_scale: float = 2.75 | |
| image_size: int = 1024 | |
| negative_prompt: str = "low quality, worst quality, over-saturated, three legs, six fingers, cartoon, anime, cgi, low res, blurry, deformed, distortion, duplicated limbs, plastic skin, jpeg artifacts, watermark" | |
| num_sample_steps: int = 50 | |
| dynamic_shift: dict | None = None | |
| _TEXT_ENCODER_DICT = { | |
| "gemma-2b": "google/gemma-2b", | |
| "gemma-2b-it": "google/gemma-2b-it", | |
| "gemma-2-2b": "google/gemma-2-2b", | |
| "gemma-2-2b-it": "Efficient-Large-Model/gemma-2-2b-it", | |
| "gemma-2-9b": "google/gemma-2-9b", | |
| "gemma-2-9b-it": "google/gemma-2-9b-it", | |
| "Qwen2-0.5B-Instruct": "Qwen/Qwen2-0.5B-Instruct", | |
| "Qwen2-1.5B-Instruct": "Qwen/Qwen2-1.5B-Instruct", | |
| } | |
| def _load_text_encoder(name: str, device: str = "cuda"): | |
| import torch.distributed as dist | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| assert name in _TEXT_ENCODER_DICT, f"Unsupported text encoder: {name}" | |
| model_id = _TEXT_ENCODER_DICT[name] | |
| is_distributed = dist.is_initialized() | |
| is_rank0 = (not is_distributed) or (dist.get_rank() == 0) | |
| if is_distributed and not is_rank0: | |
| dist.barrier() | |
| tokenizer = AutoTokenizer.from_pretrained(model_id) | |
| tokenizer.padding_side = "right" | |
| text_encoder = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.bfloat16).get_decoder().to(device) | |
| text_encoder.eval() | |
| text_encoder.requires_grad_(False) | |
| if is_distributed and is_rank0: | |
| dist.barrier() | |
| return tokenizer, text_encoder | |
| class _FlowMatchingTimescale(nn.Module): | |
| """Tiny stand-in for the deleted `FlowMatchingTrainer` — only `timescale` is read.""" | |
| def __init__(self, timescale: float): | |
| super().__init__() | |
| self.timescale = timescale | |
| class PixelDiTModel(ImaginaireModel): | |
| SUPPORTS_CONTEXT_PARALLEL: bool = False | |
| def __init__(self, config: PixelDiTModelConfig): | |
| super().__init__() | |
| self.config = config | |
| if config.dynamic_shift is not None: | |
| _ds = config.dynamic_shift | |
| logger.info( | |
| f"PixelDiT dynamic shift: base_shift={_ds['base_shift']} " | |
| f"base_image_size={_ds['base_image_size_for_shift_calc']}" | |
| ) | |
| _dtype_map = {"float32": torch.float32, "float16": torch.float16, "bfloat16": torch.bfloat16} | |
| requested_dtype = _dtype_map[config.precision] | |
| if requested_dtype != torch.float32: | |
| self.autocast_dtype = requested_dtype | |
| self.precision = torch.float32 | |
| else: | |
| self.autocast_dtype = None | |
| self.precision = torch.float32 | |
| self.tensor_kwargs = {"device": "cuda", "dtype": self.precision} | |
| with misc.timer("PixelDiTModel: build_net"): | |
| self.net = lazy_instantiate(config.net) | |
| self.net = self.net.to(device="cuda", dtype=torch.float32) | |
| self.net.requires_grad_(True) | |
| if hasattr(self.net, "init_weights"): | |
| self.net.init_weights() | |
| logger.info(f"PixDiT_T2I params: {sum(p.numel() for p in self.net.parameters()):,}") | |
| # Frozen text encoder. Use object.__setattr__ so DCP / nn.Module don't try to | |
| # register it as a child / save it in state_dict. | |
| with misc.timer("PixelDiTModel: load_text_encoder"): | |
| _tokenizer, _text_encoder = _load_text_encoder(config.text_encoder_name, device="cuda") | |
| object.__setattr__(self, "tokenizer", _tokenizer) | |
| object.__setattr__(self, "text_encoder", _text_encoder) | |
| self._chi_prompt_str = "\n".join(config.chi_prompt) if config.chi_prompt else "" | |
| self._num_chi_tokens = len(self.tokenizer.encode(self._chi_prompt_str)) if self._chi_prompt_str else 0 | |
| self._null_caption_embs = self._encode_text_raw([config.negative_prompt if config.negative_prompt else ""])[ | |
| 0 | |
| ] | |
| # Tiny flow-matching shim: only `timescale` is consumed by inference. | |
| self.fm_trainer = _FlowMatchingTimescale(config.fm_timescale) | |
| self.conditioner = lazy_instantiate(config.conditioner) | |
| logger.info(f"PixelDiT conditioner: {self.conditioner}") | |
| # --------------------------------------------------------------------- | |
| # Text encoding | |
| # --------------------------------------------------------------------- | |
| def _encode_text_raw(self, captions: list[str]) -> tuple[Tensor, Tensor]: | |
| if self._chi_prompt_str: | |
| prompts_all = [self._chi_prompt_str + cap for cap in captions] | |
| max_length_all = self._num_chi_tokens + self.config.model_max_length - 2 | |
| else: | |
| prompts_all = captions | |
| max_length_all = self.config.model_max_length | |
| caption_token = self.tokenizer( | |
| prompts_all, | |
| max_length=max_length_all, | |
| padding="max_length", | |
| truncation=True, | |
| return_tensors="pt", | |
| ).to("cuda") | |
| caption_embs = self.text_encoder(caption_token.input_ids, caption_token.attention_mask)[0] | |
| select_index = [0] + list(range(-self.config.model_max_length + 1, 0)) | |
| caption_embs = caption_embs[:, select_index] | |
| emb_masks = caption_token.attention_mask[:, select_index] | |
| return caption_embs, emb_masks | |
| def _normalize_image(self, img: Tensor) -> Tensor: | |
| if img.dtype == torch.uint8: | |
| return img.float() / 127.5 - 1.0 | |
| elif img.max() > 1.0: | |
| return img.float() / 127.5 - 1.0 | |
| else: | |
| if img.min() >= 0: | |
| return img.float() * 2.0 - 1.0 | |
| return img.float() | |
| # --------------------------------------------------------------------- | |
| # Context-parallel helpers (no-op when megatron CP isn't initialized). | |
| # --------------------------------------------------------------------- | |
| def get_context_parallel_group(): | |
| if parallel_state is not None and parallel_state.is_initialized(): | |
| return parallel_state.get_context_parallel_group() | |
| return None | |
| def _maybe_enable_cp_on_nets(self, nets: list) -> None: | |
| cp_group = self.get_context_parallel_group() | |
| for net in nets: | |
| if net is None: | |
| continue | |
| if cp_group is None or cp_group.size() <= 1: | |
| if hasattr(net, "disable_context_parallel") and getattr(net, "is_context_parallel_enabled", False): | |
| net.disable_context_parallel() | |
| else: | |
| if hasattr(net, "enable_context_parallel"): | |
| net.enable_context_parallel(cp_group) | |
| def _broadcast_tensor_for_cp(self, t: Tensor | None) -> Tensor | None: | |
| cp_group = self.get_context_parallel_group() | |
| if t is None or cp_group is None or cp_group.size() <= 1: | |
| return t | |
| from torch.distributed import get_process_group_ranks | |
| src = min(get_process_group_ranks(cp_group)) | |
| return robust_broadcast(t.contiguous(), src=src, pg=cp_group) | |
| def _broadcast_object_for_cp(self, obj): | |
| return cp_broadcast(obj, self.get_context_parallel_group()) | |
| # --------------------------------------------------------------------- | |
| # Checkpoint helpers — the distill subclass overrides these for its | |
| # net.* / fake_score.* / discriminator.* prefix routing. | |
| # --------------------------------------------------------------------- | |
| def state_dict(self, *args, **kwargs): | |
| return self.net.state_dict(prefix="net.") | |
| def load_state_dict(self, state_dict, strict=True, assign=False, **kwargs): | |
| has_core_keys = any(k.startswith("core.") for k in state_dict) | |
| has_net_keys = any(k.startswith("net.") for k in state_dict) | |
| if has_core_keys and not has_net_keys: | |
| logger.info("Loading original PixelDiT checkpoint (core.* prefix)") | |
| net_sd = {} | |
| for k, v in state_dict.items(): | |
| if k == "pos_embed": | |
| continue | |
| if k.startswith("core."): | |
| net_sd[k[len("core.") :]] = v | |
| self.net.load_state_dict(net_sd, strict=False, assign=assign) | |
| else: | |
| _net_sd = { | |
| k[len("net.") :]: v | |
| for k, v in state_dict.items() | |
| if k.startswith("net.") and not k.startswith("net_ema.") | |
| } | |
| if _net_sd: | |
| self.net.load_state_dict(_net_sd, strict=strict, assign=assign) | |