Unconditional Image Generation
Diffusers
Safetensors
image-generation
class-conditional
imagenet
dico
latent-diffusion
convnet
Instructions to use BiliSakura/DiCo-diffusers with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use BiliSakura/DiCo-diffusers with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("BiliSakura/DiCo-diffusers", dtype=torch.bfloat16, device_map="cuda") prompt = "golden retriever" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
| """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" | |
| 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) | |
| 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 | |
| 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()} | |
| 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()} | |
| 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())) | |
| 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, | |
| ) | |
| 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) | |
| 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) | |
| 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 | |