Created
August 10, 2024 19:30
-
-
Save AmericanPresidentJimmyCarter/b8e8548595099d8551c1d20447f7fa08 to your computer and use it in GitHub Desktop.
Use your flux-dev LoRA with a quantized model and CFG in <16gb VRAM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import inspect | |
from typing import Any, Callable, Dict, List, Optional, Union | |
import numpy as np | |
import torch | |
from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast | |
from diffusers.image_processor import VaeImageProcessor | |
from diffusers.loaders import FluxLoraLoaderMixin | |
from diffusers.models.autoencoders import AutoencoderKL | |
from diffusers.models.transformers import FluxTransformer2DModel | |
from diffusers.schedulers import FlowMatchEulerDiscreteScheduler | |
from diffusers.utils import ( | |
USE_PEFT_BACKEND, | |
make_image_grid, | |
scale_lora_layers, | |
unscale_lora_layers, | |
) | |
from diffusers.utils.torch_utils import randn_tensor | |
from diffusers.pipelines.pipeline_utils import DiffusionPipeline | |
from diffusers.pipelines.flux.pipeline_flux import calculate_shift, retrieve_timesteps | |
from diffusers.pipelines.flux.pipeline_output import FluxPipelineOutput | |
from optimum.quanto import quantize, qfloat8, freeze | |
class FluxPipeline(DiffusionPipeline, FluxLoraLoaderMixin): | |
r""" | |
The Flux pipeline for text-to-image generation. | |
Reference: https://blackforestlabs.ai/announcing-black-forest-labs/ | |
Args: | |
transformer ([`FluxTransformer2DModel`]): | |
Conditional Transformer (MMDiT) architecture to denoise the encoded image latents. | |
scheduler ([`FlowMatchEulerDiscreteScheduler`]): | |
A scheduler to be used in combination with `transformer` 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 ([`CLIPTextModel`]): | |
[CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically | |
the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant. | |
text_encoder_2 ([`T5EncoderModel`]): | |
[T5](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5EncoderModel), specifically | |
the [google/t5-v1_1-xxl](https://huggingface.co/google/t5-v1_1-xxl) variant. | |
tokenizer (`CLIPTokenizer`): | |
Tokenizer of class | |
[CLIPTokenizer](https://huggingface.co/docs/transformers/en/model_doc/clip#transformers.CLIPTokenizer). | |
tokenizer_2 (`T5TokenizerFast`): | |
Second Tokenizer of class | |
[T5TokenizerFast](https://huggingface.co/docs/transformers/en/model_doc/t5#transformers.T5TokenizerFast). | |
""" | |
model_cpu_offload_seq = "text_encoder->text_encoder_2->transformer->vae" | |
_optional_components = [] | |
_callback_tensor_inputs = ["latents", "prompt_embeds"] | |
def __init__( | |
self, | |
scheduler: FlowMatchEulerDiscreteScheduler, | |
vae: AutoencoderKL, | |
text_encoder: CLIPTextModel, | |
tokenizer: CLIPTokenizer, | |
text_encoder_2: T5EncoderModel, | |
tokenizer_2: T5TokenizerFast, | |
transformer: FluxTransformer2DModel, | |
): | |
super().__init__() | |
self.register_modules( | |
vae=vae, | |
text_encoder=text_encoder, | |
text_encoder_2=text_encoder_2, | |
tokenizer=tokenizer, | |
tokenizer_2=tokenizer_2, | |
transformer=transformer, | |
scheduler=scheduler, | |
) | |
self.vae_scale_factor = ( | |
2 ** (len(self.vae.config.block_out_channels)) if hasattr(self, "vae") and self.vae is not None else 16 | |
) | |
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) | |
self.tokenizer_max_length = ( | |
self.tokenizer.model_max_length if hasattr(self, "tokenizer") and self.tokenizer is not None else 77 | |
) | |
self.default_sample_size = 64 | |
def _get_t5_prompt_embeds( | |
self, | |
prompt: Union[str, List[str]] = None, | |
num_images_per_prompt: int = 1, | |
max_sequence_length: int = 512, | |
device: Optional[torch.device] = None, | |
dtype: Optional[torch.dtype] = None, | |
): | |
device = device or self._execution_device | |
dtype = dtype or self.text_encoder.dtype | |
prompt = [prompt] if isinstance(prompt, str) else prompt | |
batch_size = len(prompt) | |
text_inputs = self.tokenizer_2( | |
prompt, | |
padding="max_length", | |
max_length=max_sequence_length, | |
truncation=True, | |
return_length=False, | |
return_overflowing_tokens=False, | |
return_tensors="pt", | |
) | |
text_input_ids = text_inputs.input_ids | |
untruncated_ids = self.tokenizer_2(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_2.batch_decode(untruncated_ids[:, self.tokenizer_max_length - 1 : -1]) | |
prompt_embeds = self.text_encoder_2(text_input_ids.to(device), output_hidden_states=False)[0] | |
dtype = self.text_encoder_2.dtype | |
prompt_embeds = prompt_embeds.to(dtype=dtype, device=device) | |
_, seq_len, _ = prompt_embeds.shape | |
# duplicate text embeddings and attention mask for each generation per prompt, using mps friendly method | |
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) | |
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) | |
return prompt_embeds | |
def _get_clip_prompt_embeds( | |
self, | |
prompt: Union[str, List[str]], | |
num_images_per_prompt: int = 1, | |
device: Optional[torch.device] = None, | |
): | |
device = device or self._execution_device | |
prompt = [prompt] if isinstance(prompt, str) else prompt | |
batch_size = len(prompt) | |
text_inputs = self.tokenizer( | |
prompt, | |
padding="max_length", | |
max_length=self.tokenizer_max_length, | |
truncation=True, | |
return_overflowing_tokens=False, | |
return_length=False, | |
return_tensors="pt", | |
) | |
text_input_ids = text_inputs.input_ids | |
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[:, self.tokenizer_max_length - 1 : -1]) | |
prompt_embeds = self.text_encoder(text_input_ids.to(device), output_hidden_states=False) | |
# Use pooled output of CLIPTextModel | |
prompt_embeds = prompt_embeds.pooler_output | |
prompt_embeds = prompt_embeds.to(dtype=self.text_encoder.dtype, device=device) | |
# duplicate text embeddings for each generation per prompt, using mps friendly method | |
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) | |
prompt_embeds = prompt_embeds.view(batch_size * num_images_per_prompt, -1) | |
return prompt_embeds | |
def encode_prompt( | |
self, | |
prompt: Union[str, List[str]], | |
prompt_2: Union[str, List[str]], | |
device: Optional[torch.device] = None, | |
num_images_per_prompt: int = 1, | |
prompt_embeds: Optional[torch.FloatTensor] = None, | |
pooled_prompt_embeds: Optional[torch.FloatTensor] = None, | |
max_sequence_length: int = 512, | |
lora_scale: Optional[float] = None, | |
): | |
r""" | |
Args: | |
prompt (`str` or `List[str]`, *optional*): | |
prompt to be encoded | |
prompt_2 (`str` or `List[str]`, *optional*): | |
The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is | |
used in all text-encoders | |
device: (`torch.device`): | |
torch device | |
num_images_per_prompt (`int`): | |
number of images that should be generated per prompt | |
prompt_embeds (`torch.FloatTensor`, *optional*): | |
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not | |
provided, text embeddings will be generated from `prompt` input argument. | |
pooled_prompt_embeds (`torch.FloatTensor`, *optional*): | |
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. | |
If not provided, pooled text embeddings will be generated from `prompt` input argument. | |
lora_scale (`float`, *optional*): | |
A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. | |
""" | |
device = device or self._execution_device | |
# set lora scale so that monkey patched LoRA | |
# function of text encoder can correctly access it | |
if lora_scale is not None and isinstance(self, FluxLoraLoaderMixin): | |
self._lora_scale = lora_scale | |
# dynamically adjust the LoRA scale | |
if self.text_encoder is not None and USE_PEFT_BACKEND: | |
scale_lora_layers(self.text_encoder, lora_scale) | |
if self.text_encoder_2 is not None and USE_PEFT_BACKEND: | |
scale_lora_layers(self.text_encoder_2, lora_scale) | |
prompt = [prompt] if isinstance(prompt, str) else prompt | |
if prompt is not None: | |
batch_size = len(prompt) | |
else: | |
batch_size = prompt_embeds.shape[0] | |
if prompt_embeds is None: | |
prompt_2 = prompt_2 or prompt | |
prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2 | |
# We only use the pooled prompt output from the CLIPTextModel | |
pooled_prompt_embeds = self._get_clip_prompt_embeds( | |
prompt=prompt, | |
device=device, | |
num_images_per_prompt=num_images_per_prompt, | |
) | |
prompt_embeds = self._get_t5_prompt_embeds( | |
prompt=prompt_2, | |
num_images_per_prompt=num_images_per_prompt, | |
max_sequence_length=max_sequence_length, | |
device=device, | |
) | |
if self.text_encoder is not None: | |
if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND: | |
# Retrieve the original scale by scaling back the LoRA layers | |
unscale_lora_layers(self.text_encoder, lora_scale) | |
if self.text_encoder_2 is not None: | |
if isinstance(self, FluxLoraLoaderMixin) and USE_PEFT_BACKEND: | |
# Retrieve the original scale by scaling back the LoRA layers | |
unscale_lora_layers(self.text_encoder_2, lora_scale) | |
dtype = self.text_encoder.dtype if self.text_encoder is not None else self.transformer.dtype | |
text_ids = torch.zeros(batch_size, prompt_embeds.shape[1], 3).to(device=device, dtype=dtype) | |
text_ids = text_ids.repeat(num_images_per_prompt, 1, 1) | |
return prompt_embeds, pooled_prompt_embeds, text_ids | |
def check_inputs( | |
self, | |
prompt, | |
prompt_2, | |
height, | |
width, | |
prompt_embeds=None, | |
pooled_prompt_embeds=None, | |
callback_on_step_end_tensor_inputs=None, | |
max_sequence_length=None, | |
): | |
if height % 8 != 0 or width % 8 != 0: | |
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.") | |
if callback_on_step_end_tensor_inputs is not None and not all( | |
k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs | |
): | |
raise ValueError( | |
f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" | |
) | |
if prompt is not None and prompt_embeds is not None: | |
raise ValueError( | |
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" | |
" only forward one of the two." | |
) | |
elif prompt_2 is not None and prompt_embeds is not None: | |
raise ValueError( | |
f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to" | |
" only forward one of the two." | |
) | |
elif prompt is None and prompt_embeds is None: | |
raise ValueError( | |
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." | |
) | |
elif prompt is not None and (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)}") | |
elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)): | |
raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}") | |
if prompt_embeds is not None and pooled_prompt_embeds is None: | |
raise ValueError( | |
"If `prompt_embeds` are provided, `pooled_prompt_embeds` also have to be passed. Make sure to generate `pooled_prompt_embeds` from the same text encoder that was used to generate `prompt_embeds`." | |
) | |
if max_sequence_length is not None and max_sequence_length > 512: | |
raise ValueError(f"`max_sequence_length` cannot be greater than 512 but is {max_sequence_length}") | |
@staticmethod | |
def _prepare_latent_image_ids(batch_size, height, width, device, dtype): | |
latent_image_ids = torch.zeros(height // 2, width // 2, 3) | |
latent_image_ids[..., 1] = latent_image_ids[..., 1] + torch.arange(height // 2)[:, None] | |
latent_image_ids[..., 2] = latent_image_ids[..., 2] + torch.arange(width // 2)[None, :] | |
latent_image_id_height, latent_image_id_width, latent_image_id_channels = latent_image_ids.shape | |
latent_image_ids = latent_image_ids[None, :].repeat(batch_size, 1, 1, 1) | |
latent_image_ids = latent_image_ids.reshape( | |
batch_size, latent_image_id_height * latent_image_id_width, latent_image_id_channels | |
) | |
return latent_image_ids.to(device=device, dtype=dtype) | |
@staticmethod | |
def _pack_latents(latents, batch_size, num_channels_latents, height, width): | |
latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2) | |
latents = latents.permute(0, 2, 4, 1, 3, 5) | |
latents = latents.reshape(batch_size, (height // 2) * (width // 2), num_channels_latents * 4) | |
return latents | |
@staticmethod | |
def _unpack_latents(latents, height, width, vae_scale_factor): | |
batch_size, num_patches, channels = latents.shape | |
height = height // vae_scale_factor | |
width = width // vae_scale_factor | |
latents = latents.view(batch_size, height, width, channels // 4, 2, 2) | |
latents = latents.permute(0, 3, 1, 4, 2, 5) | |
latents = latents.reshape(batch_size, channels // (2 * 2), height * 2, width * 2) | |
return latents | |
def prepare_latents( | |
self, | |
batch_size, | |
num_channels_latents, | |
height, | |
width, | |
dtype, | |
device, | |
generator, | |
latents=None, | |
): | |
height = 2 * (int(height) // self.vae_scale_factor) | |
width = 2 * (int(width) // self.vae_scale_factor) | |
shape = (batch_size, num_channels_latents, height, width) | |
if latents is not None: | |
latent_image_ids = self._prepare_latent_image_ids(batch_size, height, width, device, dtype) | |
return latents.to(device=device, dtype=dtype), latent_image_ids | |
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." | |
) | |
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype) | |
latents = self._pack_latents(latents, batch_size, num_channels_latents, height, width) | |
latent_image_ids = self._prepare_latent_image_ids(batch_size, height, width, device, dtype) | |
return latents, latent_image_ids | |
@property | |
def guidance_scale(self): | |
return self._guidance_scale | |
@property | |
def joint_attention_kwargs(self): | |
return self._joint_attention_kwargs | |
@property | |
def num_timesteps(self): | |
return self._num_timesteps | |
@property | |
def interrupt(self): | |
return self._interrupt | |
@torch.no_grad() | |
def __call__( | |
self, | |
prompt: Union[str, List[str]] = None, | |
prompt_2: Optional[Union[str, List[str]]] = None, | |
height: Optional[int] = None, | |
width: Optional[int] = None, | |
num_inference_steps: int = 28, | |
timesteps: List[int] = None, | |
guidance_scale: float = 3.5, | |
num_images_per_prompt: Optional[int] = 1, | |
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, | |
latents: Optional[torch.FloatTensor] = None, | |
prompt_embeds: Optional[torch.FloatTensor] = None, | |
pooled_prompt_embeds: Optional[torch.FloatTensor] = None, | |
output_type: Optional[str] = "pil", | |
return_dict: bool = True, | |
joint_attention_kwargs: Optional[Dict[str, Any]] = None, | |
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, | |
callback_on_step_end_tensor_inputs: List[str] = ["latents"], | |
max_sequence_length: int = 512, | |
fake_guidance_scale: float = 3.5, | |
timestep_to_start_cfg: int = 0, | |
): | |
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`. | |
instead. | |
prompt_2 (`str` or `List[str]`, *optional*): | |
The prompt or prompts to be sent to `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is | |
will be used instead | |
height (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): | |
The height in pixels of the generated image. This is set to 1024 by default for the best results. | |
width (`int`, *optional*, defaults to self.unet.config.sample_size * self.vae_scale_factor): | |
The width in pixels of the generated image. This is set to 1024 by default for the best results. | |
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. | |
timesteps (`List[int]`, *optional*): | |
Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument | |
in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is | |
passed will be used. Must be in descending order. | |
guidance_scale (`float`, *optional*, defaults to 7.0): | |
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. | |
num_images_per_prompt (`int`, *optional*, defaults to 1): | |
The number of images to generate per prompt. | |
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`. | |
prompt_embeds (`torch.FloatTensor`, *optional*): | |
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not | |
provided, text embeddings will be generated from `prompt` input argument. | |
pooled_prompt_embeds (`torch.FloatTensor`, *optional*): | |
Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. | |
If not provided, pooled text embeddings will be generated from `prompt` input argument. | |
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.flux.FluxPipelineOutput`] instead of a plain tuple. | |
joint_attention_kwargs (`dict`, *optional*): | |
A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under | |
`self.processor` in | |
[diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). | |
callback_on_step_end (`Callable`, *optional*): | |
A function that calls at the end of each denoising steps during the inference. The function is called | |
with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int, | |
callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by | |
`callback_on_step_end_tensor_inputs`. | |
callback_on_step_end_tensor_inputs (`List`, *optional*): | |
The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list | |
will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the | |
`._callback_tensor_inputs` attribute of your pipeline class. | |
max_sequence_length (`int` defaults to 512): Maximum sequence length to use with the `prompt`. | |
Examples: | |
Returns: | |
[`~pipelines.flux.FluxPipelineOutput`] or `tuple`: [`~pipelines.flux.FluxPipelineOutput`] if `return_dict` | |
is True, otherwise a `tuple`. When returning a tuple, the first element is a list with the generated | |
images. | |
""" | |
height = height or self.default_sample_size * self.vae_scale_factor | |
width = width or self.default_sample_size * self.vae_scale_factor | |
# 1. Check inputs. Raise error if not correct | |
self.check_inputs( | |
prompt, | |
prompt_2, | |
height, | |
width, | |
prompt_embeds=prompt_embeds, | |
pooled_prompt_embeds=pooled_prompt_embeds, | |
callback_on_step_end_tensor_inputs=callback_on_step_end_tensor_inputs, | |
max_sequence_length=max_sequence_length, | |
) | |
self._guidance_scale = guidance_scale | |
self._joint_attention_kwargs = joint_attention_kwargs | |
self._interrupt = False | |
# 2. Define call parameters | |
if prompt is not None and isinstance(prompt, str): | |
batch_size = 1 | |
elif prompt is not None and isinstance(prompt, list): | |
batch_size = len(prompt) | |
else: | |
batch_size = prompt_embeds.shape[0] | |
device = self._execution_device | |
lora_scale = ( | |
self.joint_attention_kwargs.get("scale", None) if self.joint_attention_kwargs is not None else None | |
) | |
( | |
prompt_embeds, | |
pooled_prompt_embeds, | |
text_ids, | |
) = self.encode_prompt( | |
prompt=prompt, | |
prompt_2=prompt_2, | |
prompt_embeds=prompt_embeds, | |
pooled_prompt_embeds=pooled_prompt_embeds, | |
device=device, | |
num_images_per_prompt=num_images_per_prompt, | |
max_sequence_length=max_sequence_length, | |
lora_scale=lora_scale, | |
) | |
negative_prompt_embeds = None | |
negative_pooled_prompt_embeds = None | |
negative_text_ids = None | |
( | |
negative_prompt_embeds, | |
negative_pooled_prompt_embeds, | |
negative_text_ids, | |
) = self.encode_prompt( | |
prompt="", | |
prompt_2="", | |
prompt_embeds=None, | |
pooled_prompt_embeds=None, | |
device=device, | |
num_images_per_prompt=num_images_per_prompt, | |
max_sequence_length=max_sequence_length, | |
lora_scale=lora_scale, | |
) | |
# 4. Prepare latent variables | |
num_channels_latents = self.transformer.config.in_channels // 4 | |
latents, latent_image_ids = self.prepare_latents( | |
batch_size * num_images_per_prompt, | |
num_channels_latents, | |
height, | |
width, | |
prompt_embeds.dtype, | |
device, | |
generator, | |
latents, | |
) | |
# 5. Prepare timesteps | |
sigmas = np.linspace(1.0, 1 / num_inference_steps, num_inference_steps) | |
image_seq_len = latents.shape[1] | |
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) | |
# 6. Denoising loop | |
with self.progress_bar(total=num_inference_steps) as progress_bar: | |
for i, t in enumerate(timesteps): | |
if self.interrupt: | |
continue | |
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML | |
timestep = t.expand(latents.shape[0]).to(latents.dtype) | |
# handle guidance | |
if self.transformer.config.guidance_embeds: | |
guidance = torch.tensor([fake_guidance_scale], device=device) | |
guidance = guidance.expand(latents.shape[0]) | |
else: | |
guidance = None | |
noise_pred = self.transformer( | |
hidden_states=latents, | |
# YiYi notes: divide it by 1000 for now because we scale it by 1000 in the transforme rmodel (we should not keep it but I want to keep the inputs same for the model for testing) | |
timestep=timestep / 1000, | |
guidance=guidance, | |
pooled_projections=pooled_prompt_embeds, | |
encoder_hidden_states=prompt_embeds, | |
txt_ids=text_ids, | |
img_ids=latent_image_ids, | |
joint_attention_kwargs=self.joint_attention_kwargs, | |
return_dict=False, | |
)[0] | |
if i >= timestep_to_start_cfg: | |
noise_pred_uncond = self.transformer( | |
hidden_states=latents, | |
# YiYi notes: divide it by 1000 for now because we scale it by 1000 in the transforme rmodel (we should not keep it but I want to keep the inputs same for the model for testing) | |
timestep=timestep / 1000, | |
guidance=guidance, | |
pooled_projections=negative_pooled_prompt_embeds, | |
encoder_hidden_states=negative_prompt_embeds, | |
txt_ids=negative_text_ids, | |
img_ids=latent_image_ids, | |
joint_attention_kwargs=self.joint_attention_kwargs, | |
return_dict=False, | |
)[0] | |
noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred - noise_pred_uncond) | |
# compute the previous noisy sample x_t -> x_t-1 | |
latents_dtype = latents.dtype | |
latents = self.scheduler.step(noise_pred, t, latents, return_dict=False)[0] | |
if latents.dtype != latents_dtype: | |
if torch.backends.mps.is_available(): | |
# some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 | |
latents = latents.to(latents_dtype) | |
if callback_on_step_end is not None: | |
callback_kwargs = {} | |
for k in callback_on_step_end_tensor_inputs: | |
callback_kwargs[k] = locals()[k] | |
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) | |
latents = callback_outputs.pop("latents", latents) | |
prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) | |
# 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 output_type == "latent": | |
image = latents | |
else: | |
latents = self._unpack_latents(latents, height, width, self.vae_scale_factor) | |
latents = (latents / self.vae.config.scaling_factor) + self.vae.config.shift_factor | |
image = self.vae.decode(latents, return_dict=False)[0] | |
image = self.image_processor.postprocess(image, output_type=output_type) | |
# Offload all models | |
self.maybe_free_model_hooks() | |
if not return_dict: | |
return (image,) | |
return FluxPipelineOutput(images=image) | |
base_model = "black-forest-labs/FLUX.1-dev" | |
pipe = FluxPipeline.from_pretrained(base_model, torch_dtype=torch.bfloat16) | |
print('Loading and fusing lora, please wait...') | |
pipe.load_lora_weights("./pytorch_lora_weights.safetensors") | |
# We need this scaling because SimpleTuner fixes the alpha to 16, might be fixed later in diffusers | |
# See https://github.com/huggingface/diffusers/issues/9134 | |
pipe.fuse_lora(lora_scale=0.125) | |
pipe.unload_lora_weights() | |
print('Quantizing, please wait...') | |
quantize(pipe.transformer, qfloat8) | |
freeze(pipe.transformer) | |
print('Model quantized!') | |
pipe.enable_model_cpu_offload() | |
ts_cutoff = 2 | |
lora_prompts = [ | |
'In this scene from the animated series "Helluva Boss," Loona, the wolf-like receptionist of the Immediate Murder Professionals (I.M.P), is depicted leaning against a wall outside the office. She is casually engrossed in her phone, displaying her typical aloof and detached demeanor. Loona\'s appearance includes her usual whitish fur, light grey hair, black-tipped ears, and red eyes, complemented by her punk-inspired attire featuring a black choker with spikes, a dark grey top, fingerless wrist-length black gloves, and black shorts.', | |
'Loona shrugs with an exasperated expression, her red eyes wide and frustrated, as she seemingly questions or challenges something said in the I.M.P office. Still from Helluva boss. Loona\'s appearance includes her usual whitish fur, light grey hair, black-tipped ears, and red eyes, complemented by her punk-inspired attire featuring a black choker with spikes, a dark grey top, fingerless wrist-length black gloves, and black shorts.', | |
"A scene from the animated series \"Helluva Boss,\" set in the office. Loona, the wolf-like receptionist with white fur, black-tipped ears, and red eyes, is seated on a couch, facing towards the viewer. Loona\'s appearance is complemented by her punk-inspired attire featuring a black choker with spikes, a dark grey top, fingerless wrist-length black gloves, and black shorts. She holds a piece of paper that says,\"Welcome to Losercity, jerks\". In the background, the office has a striped wall pattern and visible damage on the ceiling, indicating a chaotic or rough environment. On the right side of the image, two imp characters appear to be engaged in conversation.", | |
"Loona from Helluva Boss is dressed in an oversized taco costume, looking visibly irritated and embarrassed. Her red eyes convey her annoyance as she crosses her arms and glares to the side. Loona\'s appearance includes her usual whitish fur, light grey hair, black-tipped ears, and red eyes", | |
] | |
generator = torch.Generator().manual_seed(12345) | |
images = [] | |
for prompt in lora_prompts: | |
image = pipe( | |
prompt=prompt, | |
width=1024, | |
height=1024, | |
num_inference_steps=15, | |
generator=generator, | |
guidance_scale=4.5, | |
timestep_to_start_cfg=ts_cutoff, | |
).images[0] | |
images.append(image) | |
make_image_grid(images, 2, 2).save('flux_lora_image.png') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment