Skip to content

Commit efa773a

Browse files
authored
Support K-LMS in img2img (huggingface#270)
* Support K-LMS in img2img * Apply review suggestions
1 parent da7d4cf commit efa773a

3 files changed

Lines changed: 32 additions & 8 deletions

File tree

examples/inference/image_to_image.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@
55
import torch
66

77
import PIL
8-
from diffusers import AutoencoderKL, DDIMScheduler, DiffusionPipeline, PNDMScheduler, UNet2DConditionModel
8+
from diffusers import (
9+
AutoencoderKL,
10+
DDIMScheduler,
11+
DiffusionPipeline,
12+
LMSDiscreteScheduler,
13+
PNDMScheduler,
14+
UNet2DConditionModel,
15+
)
916
from diffusers.pipelines.stable_diffusion import StableDiffusionSafetyChecker
1017
from tqdm.auto import tqdm
1118
from transformers import CLIPFeatureExtractor, CLIPTextModel, CLIPTokenizer
@@ -87,12 +94,17 @@ def __call__(
8794
# get the original timestep using init_timestep
8895
init_timestep = int(num_inference_steps * strength) + offset
8996
init_timestep = min(init_timestep, num_inference_steps)
90-
timesteps = self.scheduler.timesteps[-init_timestep]
91-
timesteps = torch.tensor([timesteps] * batch_size, dtype=torch.long, device=self.device)
97+
if isinstance(self.scheduler, LMSDiscreteScheduler):
98+
timesteps = torch.tensor(
99+
[num_inference_steps - init_timestep] * batch_size, dtype=torch.long, device=self.device
100+
)
101+
else:
102+
timesteps = self.scheduler.timesteps[-init_timestep]
103+
timesteps = torch.tensor([timesteps] * batch_size, dtype=torch.long, device=self.device)
92104

93105
# add noise to latents using the timesteps
94106
noise = torch.randn(init_latents.shape, generator=generator, device=self.device)
95-
init_latents = self.scheduler.add_noise(init_latents, noise, timesteps)
107+
init_latents = self.scheduler.add_noise(init_latents, noise, timesteps).to(self.device)
96108

97109
# get prompt text embeddings
98110
text_input = self.tokenizer(
@@ -133,8 +145,15 @@ def __call__(
133145
latents = init_latents
134146
t_start = max(num_inference_steps - init_timestep + offset, 0)
135147
for i, t in tqdm(enumerate(self.scheduler.timesteps[t_start:])):
148+
t_index = t_start + i
136149
# expand the latents if we are doing classifier free guidance
137150
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
151+
if isinstance(self.scheduler, LMSDiscreteScheduler):
152+
sigma = self.scheduler.sigmas[t_index]
153+
# the model input needs to be scaled to match the continuous ODE formulation in K-LMS
154+
latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5)
155+
latent_model_input = latent_model_input.to(self.unet.dtype)
156+
t = t.to(self.unet.dtype)
138157

139158
# predict the noise residual
140159
noise_pred = self.unet(latent_model_input, t, encoder_hidden_states=text_embeddings)["sample"]
@@ -145,11 +164,14 @@ def __call__(
145164
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
146165

147166
# compute the previous noisy sample x_t -> x_t-1
148-
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs)["prev_sample"]
167+
if isinstance(self.scheduler, LMSDiscreteScheduler):
168+
latents = self.scheduler.step(noise_pred, t_index, latents, **extra_step_kwargs)["prev_sample"]
169+
else:
170+
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs)["prev_sample"]
149171

150172
# scale and decode the image latents with vae
151173
latents = 1 / 0.18215 * latents
152-
image = self.vae.decode(latents)
174+
image = self.vae.decode(latents.to(self.vae.dtype))
153175

154176
image = (image / 2 + 0.5).clamp(0, 1)
155177
image = image.cpu().permute(0, 2, 3, 1).numpy()

src/diffusers/pipelines/stable_diffusion/pipeline_stable_diffusion.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ def __call__(
138138
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
139139
if isinstance(self.scheduler, LMSDiscreteScheduler):
140140
sigma = self.scheduler.sigmas[i]
141+
# the model input needs to be scaled to match the continuous ODE formulation in K-LMS
141142
latent_model_input = latent_model_input / ((sigma**2 + 1) ** 0.5)
142143

143144
# predict the noise residual

src/diffusers/schedulers/scheduling_lms_discrete.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,9 @@ def step(
124124
return {"prev_sample": prev_sample}
125125

126126
def add_noise(self, original_samples, noise, timesteps):
127-
sigmas = self.match_shape(self.sigmas, noise)
128-
noisy_samples = original_samples + noise * sigmas[timesteps]
127+
sigmas = self.match_shape(self.sigmas[timesteps], noise)
128+
noisy_samples = original_samples + noise * sigmas
129+
129130
return noisy_samples
130131

131132
def __len__(self):

0 commit comments

Comments
 (0)