victor's picture
victor HF Staff
Cache examples and simplify frontend
e2ddf3f verified
Raw
History Blame Contribute Delete
10.5 kB
# 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__)
@attrs.define(slots=False)
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
@attrs.define(slots=False)
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
# ---------------------------------------------------------------------
@torch.no_grad()
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).
# ---------------------------------------------------------------------
@staticmethod
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)