DiCo-diffusers / DiCo-B-256 /pipeline.py
BiliSakura's picture
Upload folder using huggingface_hub
28463c6 verified
Raw
History Blame Contribute Delete
16.6 kB
"""Hub custom pipeline: DiCoPipeline.
Load with native Hugging Face diffusers and trust_remote_code=True.
"""
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import inspect
import json
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union
import torch
from diffusers.image_processor import VaeImageProcessor
from diffusers.models import AutoencoderKL
from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput
from diffusers.schedulers import DDIMScheduler, KarrasDiffusionSchedulers
from diffusers.utils.torch_utils import randn_tensor
EXAMPLE_DOC_STRING = """
Examples:
```py
>>> from pathlib import Path
>>> from diffusers import DiffusionPipeline
>>> import torch
>>> model_dir = Path("./DiCo-XL-256").resolve()
>>> pipe = DiffusionPipeline.from_pretrained(
... str(model_dir),
... local_files_only=True,
... custom_pipeline=str(model_dir / "pipeline.py"),
... trust_remote_code=True,
... torch_dtype=torch.bfloat16,
... )
>>> pipe.to("cuda")
>>> image = pipe(
... class_labels="golden retriever",
... num_inference_steps=250,
... guidance_scale=1.4,
... generator=torch.Generator("cuda").manual_seed(0),
... ).images[0]
```
"""
class DiCoPipeline(DiffusionPipeline):
r"""
Pipeline for class-conditional image generation with DiCo (Diffusion ConvNet).
Parameters:
transformer ([`DiCoTransformer2DModel`]):
Class-conditional DiCo denoiser operating in VAE latent space.
vae ([`AutoencoderKL`]):
Variational autoencoder used to decode latents to pixels.
scheduler ([`DDIMScheduler`]):
Diffusion scheduler. Other [`KarrasDiffusionSchedulers`] can be swapped at inference time.
id2label (`dict[int, str]`, *optional*):
ImageNet class id to English label mapping. Values may contain comma-separated synonyms.
"""
model_cpu_offload_seq = "transformer->vae"
@staticmethod
def prepare_extra_step_kwargs(
scheduler: KarrasDiffusionSchedulers,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
eta: float = 0.0,
) -> Dict[str, object]:
kwargs: Dict[str, object] = {}
step_params = set(inspect.signature(scheduler.step).parameters.keys())
if "generator" in step_params:
kwargs["generator"] = generator
if "eta" in step_params:
kwargs["eta"] = eta
return kwargs
def __init__(
self,
transformer,
vae: AutoencoderKL,
scheduler: KarrasDiffusionSchedulers,
id2label: Optional[Dict[Union[int, str], str]] = None,
):
super().__init__()
if scheduler is None:
scheduler = DDIMScheduler(
num_train_timesteps=1000,
beta_start=0.0001,
beta_end=0.02,
beta_schedule="linear",
clip_sample=False,
set_alpha_to_one=True,
steps_offset=0,
prediction_type="epsilon",
)
self.register_modules(transformer=transformer, vae=vae, scheduler=scheduler)
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
self._id2label = self._normalize_id2label(id2label)
self.labels = self._build_label2id(self._id2label)
self._labels_loaded_from_model_index = bool(self._id2label)
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
model_kwargs = dict(kwargs)
transformer_subfolder = model_kwargs.pop("transformer_subfolder", None)
scheduler_subfolder = model_kwargs.pop("scheduler_subfolder", None)
vae_subfolder = model_kwargs.pop("vae_subfolder", None)
scheduler_kwargs = model_kwargs.pop("scheduler_kwargs", {})
base_path = Path(pretrained_model_name_or_path)
if transformer_subfolder is None and (base_path / "transformer").exists():
transformer_subfolder = "transformer"
if scheduler_subfolder is None and (base_path / "scheduler").exists():
scheduler_subfolder = "scheduler"
if vae_subfolder is None and (base_path / "vae").exists():
vae_subfolder = "vae"
try:
return super().from_pretrained(pretrained_model_name_or_path, **kwargs)
except Exception:
transformer_path = str(base_path / transformer_subfolder) if transformer_subfolder else pretrained_model_name_or_path
from transformer.transformer_dico import DiCoTransformer2DModel
transformer = DiCoTransformer2DModel.from_pretrained(transformer_path, **model_kwargs)
try:
scheduler = DDIMScheduler.from_pretrained(
pretrained_model_name_or_path,
subfolder=scheduler_subfolder,
**scheduler_kwargs,
)
except Exception:
scheduler = DDIMScheduler(
num_train_timesteps=1000,
beta_start=0.0001,
beta_end=0.02,
beta_schedule="linear",
clip_sample=False,
set_alpha_to_one=True,
steps_offset=0,
prediction_type="epsilon",
**scheduler_kwargs,
)
try:
vae = AutoencoderKL.from_pretrained(
pretrained_model_name_or_path,
subfolder=vae_subfolder,
**model_kwargs,
)
except Exception:
vae = AutoencoderKL.from_pretrained("stabilityai/sd-vae-ft-ema", **model_kwargs)
id2label = cls._read_id2label_from_model_index(str(base_path))
return cls(transformer=transformer, vae=vae, scheduler=scheduler, id2label=id2label)
def _ensure_labels_loaded(self) -> None:
if self._labels_loaded_from_model_index:
return
loaded = self._read_id2label_from_model_index(getattr(self.config, "_name_or_path", None))
if loaded:
self._id2label = loaded
self.labels = self._build_label2id(self._id2label)
self._labels_loaded_from_model_index = True
@staticmethod
def _normalize_id2label(id2label: Optional[Dict[Union[int, str], str]]) -> Dict[int, str]:
if not id2label:
return {}
return {int(key): value for key, value in id2label.items()}
@staticmethod
def _read_id2label_from_model_index(variant_path: Optional[str]) -> Dict[int, str]:
if not variant_path:
return {}
model_index_path = Path(variant_path).resolve() / "model_index.json"
if not model_index_path.exists():
return {}
raw = json.loads(model_index_path.read_text(encoding="utf-8"))
id2label = raw.get("id2label")
if not isinstance(id2label, dict):
return {}
return {int(key): value for key, value in id2label.items()}
@staticmethod
def _build_label2id(id2label: Dict[int, str]) -> Dict[str, int]:
label2id: Dict[str, int] = {}
for class_id, value in id2label.items():
for synonym in value.split(","):
synonym = synonym.strip()
if synonym:
label2id[synonym] = int(class_id)
return dict(sorted(label2id.items()))
@property
def id2label(self) -> Dict[int, str]:
self._ensure_labels_loaded()
return self._id2label
def get_label_ids(self, label: Union[str, List[str]]) -> List[int]:
self._ensure_labels_loaded()
if not self.labels:
raise ValueError("No English labels loaded. Ensure `id2label` exists in model_index.json.")
if isinstance(label, str):
label = [label]
missing = [item for item in label if item not in self.labels]
if missing:
preview = ", ".join(list(self.labels.keys())[:8])
raise ValueError(f"Unknown English label(s): {missing}. Example valid labels: {preview}, ...")
return [self.labels[item] for item in label]
def _normalize_class_labels(
self,
class_labels: Union[int, str, List[Union[int, str]], torch.LongTensor],
) -> torch.LongTensor:
if torch.is_tensor(class_labels):
return class_labels.to(device=self._execution_device, dtype=torch.long).reshape(-1)
if isinstance(class_labels, int):
class_label_ids = [class_labels]
elif isinstance(class_labels, str):
class_label_ids = self.get_label_ids(class_labels)
elif class_labels and isinstance(class_labels[0], str):
class_label_ids = self.get_label_ids(class_labels)
else:
class_label_ids = list(class_labels)
return torch.tensor(class_label_ids, device=self._execution_device, dtype=torch.long).reshape(-1)
def _default_image_size(self) -> int:
return int(self.transformer.config.input_size) * self.vae_scale_factor
def check_inputs(
self,
height: int,
width: int,
num_inference_steps: int,
output_type: str,
) -> None:
if num_inference_steps < 1:
raise ValueError("num_inference_steps must be >= 1.")
if output_type not in {"pil", "np", "pt", "latent"}:
raise ValueError("output_type must be one of: 'pil', 'np', 'pt', 'latent'.")
if height % self.vae_scale_factor != 0 or width % self.vae_scale_factor != 0:
raise ValueError(
f"height and width must be divisible by the VAE downsample factor {self.vae_scale_factor}."
)
latent_height = height // self.vae_scale_factor
latent_width = width // self.vae_scale_factor
expected_size = int(self.transformer.config.input_size)
if latent_height != expected_size or latent_width != expected_size:
raise ValueError(
f"Requested latent size {(latent_height, latent_width)} does not match the pretrained "
f"transformer input_size={expected_size}. Use height=width={self._default_image_size()}."
)
def prepare_latents(
self,
batch_size: int,
height: int,
width: int,
dtype: torch.dtype,
device: torch.device,
generator: Optional[Union[torch.Generator, List[torch.Generator]]],
) -> torch.Tensor:
latent_height = height // self.vae_scale_factor
latent_width = width // self.vae_scale_factor
return randn_tensor(
(batch_size, self.transformer.config.in_channels, latent_height, latent_width),
generator=generator,
device=device,
dtype=dtype,
)
@staticmethod
def _expand_timestep(timestep, batch: int, device: torch.device) -> torch.Tensor:
if not torch.is_tensor(timestep):
timestep = torch.tensor([timestep], dtype=torch.long, device=device)
elif timestep.ndim == 0:
timestep = timestep[None].to(device=device)
return timestep.expand(batch)
@staticmethod
def _prepare_model_output_for_scheduler(
model_output: torch.Tensor,
latent_channels: int,
scheduler: KarrasDiffusionSchedulers,
) -> torch.Tensor:
if model_output.shape[1] != 2 * latent_channels:
return model_output
variance_type = getattr(scheduler.config, "variance_type", None)
if scheduler.__class__.__name__ == "DDPMScheduler" and variance_type in ("learned", "learned_range"):
return model_output
model_output, _ = torch.split(model_output, latent_channels, dim=1)
return model_output
def decode_latents(self, latents: torch.Tensor, output_type: str = "pil"):
if output_type == "latent":
return latents
scaling_factor = getattr(self.vae.config, "scaling_factor", 0.18215)
image = self.vae.decode(latents / scaling_factor).sample
if output_type == "pt":
return image
return self.image_processor.postprocess(image, output_type=output_type)
@torch.inference_mode()
def __call__(
self,
class_labels: Union[int, str, List[Union[int, str]], torch.LongTensor],
height: Optional[int] = None,
width: Optional[int] = None,
num_inference_steps: int = 250,
guidance_scale: float = 1.0,
eta: float = 0.0,
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
output_type: str = "pil",
return_dict: bool = True,
) -> Union[ImagePipelineOutput, Tuple]:
default_size = self._default_image_size()
height = int(height or default_size)
width = int(width or default_size)
self.check_inputs(height, width, num_inference_steps, output_type)
device = self._execution_device
model_dtype = next(self.transformer.parameters()).dtype
class_labels_tensor = self._normalize_class_labels(class_labels)
batch_size = class_labels_tensor.numel()
latent_channels = int(self.transformer.config.in_channels)
null_class_val = int(self.transformer.config.num_classes)
do_cfg = guidance_scale > 1.0
latents = self.prepare_latents(
batch_size=batch_size,
height=height,
width=width,
dtype=model_dtype,
device=device,
generator=generator,
)
latent_model_input = torch.cat([latents] * 2) if do_cfg else latents
class_labels_input = class_labels_tensor
if do_cfg:
class_null = torch.full_like(class_labels_tensor, null_class_val)
class_labels_input = torch.cat([class_labels_tensor, class_null], dim=0)
self.scheduler.set_timesteps(num_inference_steps, device=device)
extra_step_kwargs = self.prepare_extra_step_kwargs(self.scheduler, generator=generator, eta=eta)
for t in self.progress_bar(self.scheduler.timesteps):
if do_cfg:
half = latent_model_input[: len(latent_model_input) // 2]
latent_model_input = torch.cat([half, half], dim=0)
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
timesteps = self._expand_timestep(t, latent_model_input.shape[0], latent_model_input.device)
noise_pred = self.transformer(
hidden_states=latent_model_input,
timestep=timesteps,
class_labels=class_labels_input,
return_dict=True,
).sample
if do_cfg:
eps, rest = noise_pred[:, :latent_channels], noise_pred[:, latent_channels:]
cond_eps, uncond_eps = torch.split(eps, len(eps) // 2, dim=0)
half_eps = uncond_eps + guidance_scale * (cond_eps - uncond_eps)
eps = torch.cat([half_eps, half_eps], dim=0)
noise_pred = torch.cat([eps, rest], dim=1)
model_output = self._prepare_model_output_for_scheduler(noise_pred, latent_channels, self.scheduler)
latent_model_input = self.scheduler.step(
model_output, t, latent_model_input, return_dict=True, **extra_step_kwargs
).prev_sample
if do_cfg:
latents, _ = latent_model_input.chunk(2, dim=0)
else:
latents = latent_model_input
image = self.decode_latents(latents, output_type=output_type)
self.maybe_free_model_hooks()
if not return_dict:
return (image,)
return ImagePipelineOutput(images=image)
DiCoPipelineOutput = ImagePipelineOutput