import einops import inspect import torch import numpy as np import PIL import os from dataclasses import dataclass from diffusers import AutoencoderKL, FlowMatchEulerDiscreteScheduler from diffusers.pipelines.pipeline_utils import DiffusionPipeline from diffusers.utils import ( CONFIG_NAME, DEPRECATED_REVISION_ARGS, BaseOutput, PushToHubMixin, deprecate, is_accelerate_available, is_accelerate_version, is_torch_npu_available, is_torch_version, logging, numpy_to_pil, replace_example_docstring, ) from diffusers.models.modeling_utils import _LOW_CPU_MEM_USAGE_DEFAULT, ModelMixin from diffusers.utils.torch_utils import randn_tensor from diffusers.utils import BaseOutput # from diffusers.image_processor import VaeImageProcessor from transformers import T5EncoderModel, T5Tokenizer from typing import Any, Callable, Dict, List, Optional, Union from PIL import Image from onediffusion.models.denoiser.nextdit import NextDiT from onediffusion.dataset.utils import * from onediffusion.dataset.multitask.multiview import calculate_rays from onediffusion.diffusion.pipelines.image_processor import VaeImageProcessorOneDiffuser logger = logging.get_logger(__name__) # pylint: disable=invalid-name SUPPORTED_DEVICE_MAP = ["balanced"] EXAMPLE_DOC_STRING = """ Examples: ```py >>> import torch >>> from one_diffusion import OneDiffusionPipeline >>> pipe = OneDiffusionPipeline.from_pretrained("path_to_one_diffuser_model") >>> pipe = pipe.to("cuda") >>> prompt = "A beautiful sunset over the ocean" >>> image = pipe(prompt).images[0] >>> image.save("beautiful_sunset.png") ``` """ def create_c2w_matrix(azimuth_deg, elevation_deg, distance=1.0, target=np.array([0, 0, 0])): """ Create a Camera-to-World (C2W) matrix from azimuth and elevation angles. Parameters: - azimuth_deg: Azimuth angle in degrees. - elevation_deg: Elevation angle in degrees. - distance: Distance from the target point. - target: The point the camera is looking at in world coordinates. Returns: - C2W: A 4x4 NumPy array representing the Camera-to-World transformation matrix. """ # Convert angles from degrees to radians azimuth = np.deg2rad(azimuth_deg) elevation = np.deg2rad(elevation_deg) # Spherical to Cartesian conversion for camera position x = distance * np.cos(elevation) * np.cos(azimuth) y = distance * np.cos(elevation) * np.sin(azimuth) z = distance * np.sin(elevation) camera_position = np.array([x, y, z]) # Define the forward vector (from camera to target) target = 2*camera_position - target forward = target - camera_position forward /= np.linalg.norm(forward) # Define the world up vector world_up = np.array([0, 0, 1]) # Compute the right vector right = np.cross(world_up, forward) if np.linalg.norm(right) < 1e-6: # Handle the singularity when forward is parallel to world_up world_up = np.array([0, 1, 0]) right = np.cross(world_up, forward) right /= np.linalg.norm(right) # Recompute the orthogonal up vector up = np.cross(forward, right) # Construct the rotation matrix rotation = np.vstack([right, up, forward]).T # 3x3 # Construct the full C2W matrix C2W = np.eye(4) C2W[:3, :3] = rotation C2W[:3, 3] = camera_position return C2W @dataclass class OneDiffusionPipelineOutput(BaseOutput): """ Output class for Stable Diffusion pipelines. Args: images (`List[PIL.Image.Image]` or `np.ndarray`) List of denoised PIL images of length `batch_size` or numpy array of shape `(batch_size, height, width, num_channels)`. PIL images or numpy array present the denoised images of the diffusion pipeline. """ images: Union[List[Image.Image], np.ndarray] latents: Optional[torch.Tensor] = None def retrieve_latents( encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample" ): if hasattr(encoder_output, "latent_dist") and sample_mode == "sample": return encoder_output.latent_dist.sample(generator) elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax": return encoder_output.latent_dist.mode() elif hasattr(encoder_output, "latents"): return encoder_output.latents else: raise AttributeError("Could not access latents of provided encoder_output") def calculate_shift( image_seq_len, base_seq_len: int = 256, max_seq_len: int = 4096, base_shift: float = 0.5, max_shift: float = 1.16, # max_clip: float = 1.5, ): m = (max_shift - base_shift) / (max_seq_len - base_seq_len) # 0.000169270833 b = base_shift - m * base_seq_len # 0.5-0.0433333332 mu = image_seq_len * m + b # mu = min(mu, max_clip) return mu # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps def retrieve_timesteps( scheduler, num_inference_steps: Optional[int] = None, device: Optional[Union[str, torch.device]] = None, timesteps: Optional[List[int]] = None, sigmas: Optional[List[float]] = None, **kwargs, ): """ Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. Args: scheduler (`SchedulerMixin`): The scheduler to get timesteps from. num_inference_steps (`int`): The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps` must be `None`. device (`str` or `torch.device`, *optional*): The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. timesteps (`List[int]`, *optional*): Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed, `num_inference_steps` and `sigmas` must be `None`. sigmas (`List[float]`, *optional*): Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed, `num_inference_steps` and `timesteps` must be `None`. Returns: `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the second element is the number of inference steps. """ if timesteps is not None and sigmas is not None: raise ValueError("Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values") if timesteps is not None: accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) if not accepts_timesteps: raise ValueError( f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" f" timestep schedules. Please check whether you are using the correct scheduler." ) scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) timesteps = scheduler.timesteps num_inference_steps = len(timesteps) elif sigmas is not None: accept_sigmas = "sigmas" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) if not accept_sigmas: raise ValueError( f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" f" sigmas schedules. Please check whether you are using the correct scheduler." ) scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs) timesteps = scheduler.timesteps num_inference_steps = len(timesteps) else: scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) timesteps = scheduler.timesteps return timesteps, num_inference_steps class OneDiffusionPipeline(DiffusionPipeline): r""" Pipeline for text-to-image generation using OneDiffuser. This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.) Args: transformer ([`NextDiT`]): Conditional transformer (NextDiT) architecture to denoise the encoded image latents. vae ([`AutoencoderKL`]): Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations. text_encoder ([`T5EncoderModel`]): Frozen text-encoder. OneDiffuser uses the T5 model as text encoder. tokenizer (`T5Tokenizer`): Tokenizer of class T5Tokenizer. scheduler ([`FlowMatchEulerDiscreteScheduler`]): A scheduler to be used in combination with `transformer` to denoise the encoded image latents. """ def __init__( self, transformer: NextDiT, vae: AutoencoderKL, text_encoder: T5EncoderModel, tokenizer: T5Tokenizer, scheduler: FlowMatchEulerDiscreteScheduler, ): super().__init__() self.register_modules( transformer=transformer, vae=vae, text_encoder=text_encoder, tokenizer=tokenizer, scheduler=scheduler, ) self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) self.image_processor = VaeImageProcessorOneDiffuser(vae_scale_factor=self.vae_scale_factor) def enable_vae_slicing(self): self.vae.enable_slicing() def disable_vae_slicing(self): self.vae.disable_slicing() def enable_sequential_cpu_offload(self, gpu_id=0): if is_accelerate_available(): from accelerate import cpu_offload else: raise ImportError("Please install accelerate via `pip install accelerate`") device = torch.device(f"cuda:{gpu_id}") for cpu_offloaded_model in [self.transformer, self.text_encoder, self.vae]: if cpu_offloaded_model is not None: cpu_offload(cpu_offloaded_model, device) @property def _execution_device(self): if self.device != torch.device("meta") or not hasattr(self.transformer, "_hf_hook"): return self.device for module in self.transformer.modules(): if ( hasattr(module, "_hf_hook") and hasattr(module._hf_hook, "execution_device") and module._hf_hook.execution_device is not None ): return torch.device(module._hf_hook.execution_device) return self.device def encode_prompt( self, prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt=None, max_length=300, ): batch_size = len(prompt) if isinstance(prompt, list) else 1 text_inputs = self.tokenizer( prompt, padding="max_length", max_length=max_length, truncation=True, add_special_tokens=True, return_tensors="pt", ) text_input_ids = text_inputs.input_ids attention_mask = text_inputs.attention_mask untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(text_input_ids, untruncated_ids): removed_text = self.tokenizer.batch_decode(untruncated_ids[:, max_length - 1 : -1]) logger.warning( "The following part of your input was truncated because CLIP can only handle sequences up to" f" {max_length} tokens: {removed_text}" ) text_encoder_output = self.text_encoder(text_input_ids.to(device), attention_mask=attention_mask.to(device)) prompt_embeds = text_encoder_output[0].to(torch.float32) # duplicate text embeddings for each generation per prompt, using mps friendly method bs_embed, seq_len, _ = prompt_embeds.shape prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) # duplicate attention mask for each generation per prompt attention_mask = attention_mask.repeat(1, num_images_per_prompt) attention_mask = attention_mask.view(bs_embed * num_images_per_prompt, -1) # get unconditional embeddings for classifier free guidance if do_classifier_free_guidance: uncond_tokens: List[str] if negative_prompt is None: uncond_tokens = [""] * batch_size elif type(prompt) is not type(negative_prompt): raise TypeError( f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" f" {type(prompt)}." ) elif isinstance(negative_prompt, str): uncond_tokens = [negative_prompt] elif batch_size != len(negative_prompt): raise ValueError( f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" " the batch size of `prompt`." ) else: uncond_tokens = negative_prompt max_length = text_input_ids.shape[-1] uncond_input = self.tokenizer( uncond_tokens, padding="max_length", max_length=max_length, truncation=True, return_tensors="pt", ) uncond_encoder_output = self.text_encoder(uncond_input.input_ids.to(device), attention_mask=uncond_input.attention_mask.to(device)) negative_prompt_embeds = uncond_encoder_output[0].to(torch.float32) # duplicate unconditional embeddings for each generation per prompt, using mps friendly method seq_len = negative_prompt_embeds.shape[1] negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) # duplicate unconditional attention mask for each generation per prompt uncond_attention_mask = uncond_input.attention_mask.repeat(1, num_images_per_prompt) uncond_attention_mask = uncond_attention_mask.view(batch_size * num_images_per_prompt, -1) # For classifier free guidance, we need to do two forward passes. # Here we concatenate the unconditional and text embeddings into a single batch # to avoid doing two forward passes prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds]) attention_mask = torch.cat([uncond_attention_mask, attention_mask]) return prompt_embeds.to(device), attention_mask.to(device) def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None): shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) if isinstance(generator, list) and len(generator) != batch_size: raise ValueError( f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" f" size of {batch_size}. Make sure the batch size matches the length of the generators." ) if latents is None: latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) else: latents = latents.to(device) # scale the initial noise by the standard deviation required by the scheduler latents = latents * self.scheduler.init_noise_sigma return latents @torch.no_grad() @replace_example_docstring(EXAMPLE_DOC_STRING) def __call__( self, prompt: Union[str, List[str]] = None, height: Optional[int] = None, width: Optional[int] = None, num_inference_steps: int = 50, guidance_scale: float = 5.0, negative_prompt: Optional[Union[str, List[str]]] = None, num_images_per_prompt: Optional[int] = 1, eta: float = 0.0, generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, latents: Optional[torch.FloatTensor] = None, output_type: Optional[str] = "pil", return_dict: bool = True, callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, callback_steps: int = 1, forward_kwargs: Optional[Dict[str, Any]] = {}, **kwargs, ): r""" Function invoked when calling the pipeline for generation. Args: prompt (`str` or `List[str]`, *optional*): The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. height (`int`, *optional*, defaults to self.transformer.config.sample_size): The height in pixels of the generated image. width (`int`, *optional*, defaults to self.transformer.config.sample_size): The width in pixels of the generated image. num_inference_steps (`int`, *optional*, defaults to 50): The number of denoising steps. More denoising steps usually lead to a higher quality image at the expense of slower inference. guidance_scale (`float`, *optional*, defaults to 7.5): Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). `guidance_scale` is defined as `w` of equation 2. of [Imagen Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, usually at the expense of lower image quality. negative_prompt (`str` or `List[str]`, *optional*): The prompt or prompts not to guide the image generation. If not defined, one has to pass `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is less than `1`). num_images_per_prompt (`int`, *optional*, defaults to 1): The number of images to generate per prompt. eta (`float`, *optional*, defaults to 0.0): Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to [`schedulers.DDIMScheduler`], will be ignored for others. generator (`torch.Generator` or `List[torch.Generator]`, *optional*): One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make generation deterministic. latents (`torch.FloatTensor`, *optional*): Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image generation. Can be used to tweak the same generation with different prompts. If not provided, a latents tensor will ge generated by sampling using the supplied random `generator`. output_type (`str`, *optional*, defaults to `"pil"`): The output format of the generate image. Choose between [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. return_dict (`bool`, *optional*, defaults to `True`): Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a plain tuple. callback (`Callable`, *optional*): A function that will be called every `callback_steps` steps during inference. The function will be called with the following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`. callback_steps (`int`, *optional*, defaults to 1): The frequency at which the `callback` function will be called. If not specified, the callback will be called at every step. Examples: Returns: [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`: [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] if `return_dict` is True, otherwise a `tuple. When returning a tuple, the first element is a list with the generated images, and the second element is a list of `bool`s denoting whether the corresponding generated image likely represents "not-safe-for-work" (nsfw) content, according to the `safety_checker`. """ height = height or self.transformer.config.input_size[-2] * 8 # TODO: Hardcoded downscale factor of vae width = width or self.transformer.config.input_size[-1] * 8 # check inputs. Raise error if not correct self.check_inputs(prompt, height, width, callback_steps) # define call parameters batch_size = 1 if isinstance(prompt, str) else len(prompt) device = self._execution_device # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf do_classifier_free_guidance = guidance_scale > 1.0 encoder_hidden_states, encoder_attention_mask = self.encode_prompt( prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt, ) # set timesteps # # self.scheduler.set_timesteps(num_inference_steps, device=device) # timesteps = self.scheduler.timesteps timesteps = None # prepare latent variables num_channels_latents = self.transformer.config.in_channels latents = self.prepare_latents( batch_size * num_images_per_prompt, num_channels_latents, height, width, self.dtype, device, generator, latents, ) # prepare extra step kwargs extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) # 5. Prepare timesteps sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) image_seq_len = latents.shape[-1] * latents.shape[-2] / self.transformer.config.patch_size[-1] / self.transformer.config.patch_size[-2] mu = calculate_shift( image_seq_len, self.scheduler.config.base_image_seq_len, self.scheduler.config.max_image_seq_len, self.scheduler.config.base_shift, self.scheduler.config.max_shift, ) timesteps, num_inference_steps = retrieve_timesteps( self.scheduler, num_inference_steps, device, timesteps, sigmas, mu=mu, ) num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) self._num_timesteps = len(timesteps) # denoising loop num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order with self.progress_bar(total=num_inference_steps) as progress_bar: for i, t in enumerate(timesteps): # expand the latents if we are doing classifier free guidance latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents # latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) # predict the noise residual noise_pred = self.transformer( samples=latent_model_input.to(self.dtype), timesteps=torch.tensor([t] * latent_model_input.shape[0], device=device), encoder_hidden_states=encoder_hidden_states.to(self.dtype), encoder_attention_mask=encoder_attention_mask, **forward_kwargs ) # perform guidance if do_classifier_free_guidance: noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample # call the callback, if provided if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): progress_bar.update() if callback is not None and i % callback_steps == 0: callback(i, t, latents) # scale and decode the image latents with vae latents = 1 / self.vae.config.scaling_factor * latents if latents.ndim == 5: latents = latents.squeeze(1) image = self.vae.decode(latents.to(self.vae.dtype)).sample image = (image / 2 + 0.5).clamp(0, 1) image = image.cpu().permute(0, 2, 3, 1).float().numpy() if output_type == "pil": image = self.numpy_to_pil(image) if not return_dict: return (image, None) return OneDiffusionPipelineOutput(images=image) @torch.no_grad() def img2img( self, prompt: Union[str, List[str]] = None, image: Union[PIL.Image.Image, List[PIL.Image.Image]] = None, height: Optional[int] = None, width: Optional[int] = None, num_inference_steps: int = 50, guidance_scale: float = 5.0, denoise_mask: Optional[List[int]] = [1, 0], negative_prompt: Optional[Union[str, List[str]]] = None, num_images_per_prompt: Optional[int] = 1, eta: float = 0.0, generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, latents: Optional[torch.FloatTensor] = None, output_type: Optional[str] = "pil", return_dict: bool = True, callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, callback_steps: int = 1, do_crop: bool = True, is_multiview: bool = False, multiview_azimuths: Optional[List[int]] = [0, 30, 60, 90], multiview_elevations: Optional[List[int]] = [0, 0, 0, 0], multiview_distances: float = 1.7, multiview_c2ws: Optional[List[torch.Tensor]] = None, multiview_intrinsics: Optional[torch.Tensor] = None, multiview_focal_length: float = 1.3887, forward_kwargs: Optional[Dict[str, Any]] = {}, noise_scale: float = 1.0, **kwargs, ): # Convert single image to list for consistent handling if isinstance(image, PIL.Image.Image): image = [image] if height is None or width is None: closest_ar = get_closest_ratio(height=image[0].size[1], width=image[0].size[0], ratios=ASPECT_RATIO_512) height, width = int(closest_ar[0][0]), int(closest_ar[0][1]) if not isinstance(multiview_distances, list) and not isinstance(multiview_distances, tuple): multiview_distances = [multiview_distances] * len(multiview_azimuths) # height = height or self.transformer.config.input_size[-2] * 8 # TODO: Hardcoded downscale factor of vae # width = width or self.transformer.config.input_size[-1] * 8 # 1. check inputs. Raise error if not correct self.check_inputs(prompt, height, width, callback_steps) # Additional input validation for image list if not all(isinstance(img, PIL.Image.Image) for img in image): raise ValueError("All elements in image list must be PIL.Image objects") # 2. define call parameters batch_size = 1 if isinstance(prompt, str) else len(prompt) device = self._execution_device do_classifier_free_guidance = guidance_scale > 1.0 # 3. Encode input prompt encoder_hidden_states, encoder_attention_mask = self.encode_prompt( prompt, device, num_images_per_prompt, do_classifier_free_guidance, negative_prompt, ) # 4. Preprocess all images if image is not None and len(image) > 0: processed_image = self.image_processor.preprocess(image, height=height, width=width, do_crop=do_crop) else: processed_image = None # # Stack processed images along the sequence dimension # if len(processed_images) > 1: # processed_image = torch.cat(processed_images, dim=0) # else: # processed_image = processed_images[0] timesteps = None # 6. prepare latent variables num_channels_latents = self.transformer.config.in_channels if processed_image is not None: cond_latents = self.prepare_latents( batch_size * num_images_per_prompt, num_channels_latents, height, width, self.dtype, device, generator, latents, image=processed_image, ) else: cond_latents = None # 7. prepare extra step kwargs extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) denoise_mask = torch.tensor(denoise_mask, device=device) denoise_indices = torch.where(denoise_mask == 1)[0] cond_indices = torch.where(denoise_mask == 0)[0] seq_length = denoise_mask.shape[0] latents = self.prepare_init_latents( batch_size * num_images_per_prompt, seq_length, num_channels_latents, height, width, self.dtype, device, generator, ) # 5. Prepare timesteps sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) # image_seq_len = latents.shape[1] * latents.shape[-1] * latents.shape[-2] / self.transformer.config.patch_size[-1] / self.transformer.config.patch_size[-2] image_seq_len = noise_scale * sum(denoise_mask) * latents.shape[-1] * latents.shape[-2] / self.transformer.config.patch_size[-1] / self.transformer.config.patch_size[-2] # image_seq_len = 256 mu = calculate_shift( image_seq_len, self.scheduler.config.base_image_seq_len, self.scheduler.config.max_image_seq_len, self.scheduler.config.base_shift, self.scheduler.config.max_shift, ) timesteps, num_inference_steps = retrieve_timesteps( self.scheduler, num_inference_steps, device, timesteps, sigmas, mu=mu, ) num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) self._num_timesteps = len(timesteps) if is_multiview: cond_indices_images = [index // 2 for index in cond_indices if index % 2 == 0] cond_indices_rays = [index // 2 for index in cond_indices if index % 2 == 1] multiview_elevations = [element for element in multiview_elevations if element is not None] multiview_azimuths = [element for element in multiview_azimuths if element is not None] multiview_distances = [element for element in multiview_distances if element is not None] if multiview_c2ws is None: multiview_c2ws = [ torch.tensor(create_c2w_matrix(azimuth, elevation, distance)) for azimuth, elevation, distance in zip(multiview_azimuths, multiview_elevations, multiview_distances) ] c2ws = torch.stack(multiview_c2ws).float() else: c2ws = torch.Tensor(multiview_c2ws).float() c2ws[:, 0:3, 1:3] *= -1 c2ws = c2ws[:, [1, 0, 2, 3], :] c2ws[:, 2, :] *= -1 w2cs = torch.inverse(c2ws) if multiview_intrinsics is None: multiview_intrinsics = torch.Tensor([[[multiview_focal_length, 0, 0.5], [0, multiview_focal_length, 0.5], [0, 0, 1]]]).repeat(c2ws.shape[0], 1, 1) K = multiview_intrinsics Rs = w2cs[:, :3, :3] Ts = w2cs[:, :3, 3] sizes = torch.Tensor([[1, 1]]).repeat(c2ws.shape[0], 1) assert height == width cond_rays = calculate_rays(K, sizes, Rs, Ts, height // 8) cond_rays = cond_rays.reshape(-1, height // 8, width // 8, 6) # padding = (0, 10) # cond_rays = torch.nn.functional.pad(cond_rays, padding, "constant", 0) cond_rays = torch.cat([cond_rays, cond_rays, cond_rays[..., :4]], dim=-1) * 1.658 cond_rays = cond_rays[None].repeat(batch_size * num_images_per_prompt, 1, 1, 1, 1) cond_rays = cond_rays.permute(0, 1, 4, 2, 3) cond_rays = cond_rays.to(device, dtype=self.dtype) latents = einops.rearrange(latents, "b (f n) c h w -> b f n c h w", n=2) if cond_latents is not None: latents[:, cond_indices_images, 0] = cond_latents latents[:, cond_indices_rays, 1] = cond_rays latents = einops.rearrange(latents, "b f n c h w -> b (f n) c h w") else: if cond_latents is not None: latents[:, cond_indices] = cond_latents # denoising loop num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order with self.progress_bar(total=num_inference_steps) as progress_bar: for i, t in enumerate(timesteps): # expand the latents if we are doing classifier free guidance latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents input_t = torch.broadcast_to(einops.repeat(torch.Tensor([t]).to(device), "1 -> 1 f 1 1 1", f=latent_model_input.shape[1]), latent_model_input.shape).clone() if is_multiview: input_t = einops.rearrange(input_t, "b (f n) c h w -> b f n c h w", n=2) input_t[:, cond_indices_images, 0] = self.scheduler.timesteps[-1] input_t[:, cond_indices_rays, 1] = self.scheduler.timesteps[-1] input_t = einops.rearrange(input_t, "b f n c h w -> b (f n) c h w") else: input_t[:, cond_indices] = self.scheduler.timesteps[-1] # predict the noise residual noise_pred = self.transformer( samples=latent_model_input.to(self.dtype), timesteps=input_t, encoder_hidden_states=encoder_hidden_states.to(self.dtype), encoder_attention_mask=encoder_attention_mask, **forward_kwargs ) # perform guidance if do_classifier_free_guidance: noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) # compute the previous noisy sample x_t -> x_t-1 bs, n_frame = noise_pred.shape[:2] noise_pred = einops.rearrange(noise_pred, "b f c h w -> (b f) c h w") latents = einops.rearrange(latents, "b f c h w -> (b f) c h w") latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample latents = einops.rearrange(latents, "(b f) c h w -> b f c h w", b=bs, f=n_frame) if is_multiview: latents = einops.rearrange(latents, "b (f n) c h w -> b f n c h w", n=2) if cond_latents is not None: latents[:, cond_indices_images, 0] = cond_latents latents[:, cond_indices_rays, 1] = cond_rays latents = einops.rearrange(latents, "b f n c h w -> b (f n) c h w") else: if cond_latents is not None: latents[:, cond_indices] = cond_latents # call the callback, if provided if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): progress_bar.update() if callback is not None and i % callback_steps == 0: callback(i, t, latents) decoded_latents = latents / 1.658 # scale and decode the image latents with vae latents = 1 / self.vae.config.scaling_factor * latents if latents.ndim == 5: latents = latents[:, denoise_indices] latents = einops.rearrange(latents, "b f c h w -> (b f) c h w") image = self.vae.decode(latents.to(self.vae.dtype)).sample image = (image / 2 + 0.5).clamp(0, 1) image = image.cpu().permute(0, 2, 3, 1).float().numpy() if output_type == "pil": image = self.numpy_to_pil(image) if not return_dict: return (image, None) return OneDiffusionPipelineOutput(images=image, latents=decoded_latents) def prepare_extra_step_kwargs(self, generator, eta): # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 # and should be between [0, 1] accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) extra_step_kwargs = {} if accepts_eta: extra_step_kwargs["eta"] = eta # check if the scheduler accepts generator accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) if accepts_generator: extra_step_kwargs["generator"] = generator return extra_step_kwargs def check_inputs(self, prompt, height, width, callback_steps): if not isinstance(prompt, str) and not isinstance(prompt, list): raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") if height % 16 != 0 or width % 16 != 0: raise ValueError(f"`height` and `width` have to be divisible by 16 but are {height} and {width}.") if (callback_steps is None) or ( callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0) ): raise ValueError( f"`callback_steps` has to be a positive integer but is {callback_steps} of type" f" {type(callback_steps)}." ) def get_timesteps(self, num_inference_steps, strength, device): # get the original timestep using init_timestep init_timestep = min(int(num_inference_steps * strength), num_inference_steps) t_start = max(num_inference_steps - init_timestep, 0) timesteps = self.scheduler.timesteps[t_start:] return timesteps, num_inference_steps - t_start def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None, image=None): shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) if isinstance(generator, list) and len(generator) != batch_size: raise ValueError( f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" f" size of {batch_size}. Make sure the batch size matches the length of the generators." ) if latents is None: latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) else: latents = latents.to(device) if image is None: # scale the initial noise by the standard deviation required by the scheduler # latents = latents * self.scheduler.init_noise_sigma return latents image = image.to(device=device, dtype=dtype) if isinstance(generator, list) and len(generator) != batch_size: raise ValueError( f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" f" size of {batch_size}. Make sure the batch size matches the length of the generators." ) elif isinstance(generator, list): if image.shape[0] < batch_size and batch_size % image.shape[0] == 0: image = torch.cat([image] * (batch_size // image.shape[0]), dim=0) elif image.shape[0] < batch_size and batch_size % image.shape[0] != 0: raise ValueError( f"Cannot duplicate `image` of batch size {image.shape[0]} to effective batch_size {batch_size} " ) init_latents = [ retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i]) for i in range(batch_size) ] init_latents = torch.cat(init_latents, dim=0) else: init_latents = retrieve_latents(self.vae.encode(image.to(self.vae.dtype)), generator=generator) init_latents = self.vae.config.scaling_factor * init_latents init_latents = init_latents.to(device=device, dtype=dtype) init_latents = einops.rearrange(init_latents, "(bs views) c h w -> bs views c h w", bs=batch_size, views=init_latents.shape[0]//batch_size) # latents = einops.rearrange(latents, "b c h w -> b 1 c h w") # latents = torch.concat([latents, init_latents], dim=1) return init_latents def prepare_init_latents(self, batch_size, seq_length, num_channels_latents, height, width, dtype, device, generator, latents=None): shape = (batch_size, seq_length, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor) if isinstance(generator, list) and len(generator) != batch_size: raise ValueError( f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" f" size of {batch_size}. Make sure the batch size matches the length of the generators." ) if latents is None: latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) else: latents = latents.to(device) return latents @torch.no_grad() def generate( self, prompt: Union[str, List[str]], num_inference_steps: int = 50, guidance_scale: float = 5.0, negative_prompt: Optional[Union[str, List[str]]] = None, num_images_per_prompt: Optional[int] = 1, height: Optional[int] = None, width: Optional[int] = None, eta: float = 0.0, generator: Optional[torch.Generator] = None, latents: Optional[torch.FloatTensor] = None, output_type: Optional[str] = "pil", return_dict: bool = True, callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None, callback_steps: Optional[int] = 1, ): """ Function for image generation using the OneDiffusionPipeline. """ return self( prompt=prompt, num_inference_steps=num_inference_steps, guidance_scale=guidance_scale, negative_prompt=negative_prompt, num_images_per_prompt=num_images_per_prompt, height=height, width=width, eta=eta, generator=generator, latents=latents, output_type=output_type, return_dict=return_dict, callback=callback, callback_steps=callback_steps, ) @staticmethod def numpy_to_pil(images): """ Convert a numpy image or a batch of images to a PIL image. """ if images.ndim == 3: images = images[None, ...] images = (images * 255).round().astype("uint8") if images.shape[-1] == 1: # special case for grayscale (single channel) images pil_images = [Image.fromarray(image.squeeze(), mode="L") for image in images] else: pil_images = [Image.fromarray(image) for image in images] return pil_images @classmethod def from_pretrained(cls, pretrained_model_name_or_path, **kwargs): model_path = pretrained_model_name_or_path cache_dir = kwargs.pop("cache_dir", None) force_download = kwargs.pop("force_download", False) proxies = kwargs.pop("proxies", None) local_files_only = kwargs.pop("local_files_only", None) token = kwargs.pop("token", None) revision = kwargs.pop("revision", None) from_flax = kwargs.pop("from_flax", False) torch_dtype = kwargs.pop("torch_dtype", None) custom_pipeline = kwargs.pop("custom_pipeline", None) custom_revision = kwargs.pop("custom_revision", None) provider = kwargs.pop("provider", None) sess_options = kwargs.pop("sess_options", None) device_map = kwargs.pop("device_map", None) max_memory = kwargs.pop("max_memory", None) offload_folder = kwargs.pop("offload_folder", None) offload_state_dict = kwargs.pop("offload_state_dict", False) low_cpu_mem_usage = kwargs.pop("low_cpu_mem_usage", _LOW_CPU_MEM_USAGE_DEFAULT) variant = kwargs.pop("variant", None) use_safetensors = kwargs.pop("use_safetensors", None) use_onnx = kwargs.pop("use_onnx", None) load_connected_pipeline = kwargs.pop("load_connected_pipeline", False) if low_cpu_mem_usage and not is_accelerate_available(): low_cpu_mem_usage = False logger.warning( "Cannot initialize model with low cpu memory usage because `accelerate` was not found in the" " environment. Defaulting to `low_cpu_mem_usage=False`. It is strongly recommended to install" " `accelerate` for faster and less memory-intense model loading. You can do so with: \n```\npip" " install accelerate\n```\n." ) if low_cpu_mem_usage is True and not is_torch_version(">=", "1.9.0"): raise NotImplementedError( "Low memory initialization requires torch >= 1.9.0. Please either update your PyTorch version or set" " `low_cpu_mem_usage=False`." ) if device_map is not None and not is_torch_version(">=", "1.9.0"): raise NotImplementedError( "Loading and dispatching requires torch >= 1.9.0. Please either update your PyTorch version or set" " `device_map=None`." ) if device_map is not None and not is_accelerate_available(): raise NotImplementedError( "Using `device_map` requires the `accelerate` library. Please install it using: `pip install accelerate`." ) if device_map is not None and not isinstance(device_map, str): raise ValueError("`device_map` must be a string.") if device_map is not None and device_map not in SUPPORTED_DEVICE_MAP: raise NotImplementedError( f"{device_map} not supported. Supported strategies are: {', '.join(SUPPORTED_DEVICE_MAP)}" ) if device_map is not None and device_map in SUPPORTED_DEVICE_MAP: if is_accelerate_version("<", "0.28.0"): raise NotImplementedError("Device placement requires `accelerate` version `0.28.0` or later.") if low_cpu_mem_usage is False and device_map is not None: raise ValueError( f"You cannot set `low_cpu_mem_usage` to False while using device_map={device_map} for loading and" " dispatching. Please make sure to set `low_cpu_mem_usage=True`." ) transformer = NextDiT.from_pretrained(f"{model_path}", subfolder="transformer", torch_dtype=torch.float32, cache_dir=cache_dir) vae = AutoencoderKL.from_pretrained(f"{model_path}", subfolder="vae", cache_dir=cache_dir) text_encoder = T5EncoderModel.from_pretrained(f"{model_path}", subfolder="text_encoder", torch_dtype=torch.float16, cache_dir=cache_dir) tokenizer = T5Tokenizer.from_pretrained(model_path, subfolder="tokenizer", cache_dir=cache_dir) scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(model_path, subfolder="scheduler", cache_dir=cache_dir) pipeline = cls( transformer=transformer, vae=vae, text_encoder=text_encoder, tokenizer=tokenizer, scheduler=scheduler, **kwargs ) return pipeline