Skip to content

Commit 17f9aed

Browse files
[Scheduler] DPM-Solver (++) Inverse Scheduler (#3335)
* Add DPM-Solver Multistep Inverse Scheduler * Add draft tests for DiffEdit * Add inverse sde-dpmsolver steps to tune image diversity from inverted latents * Fix tests --------- Co-authored-by: Patrick von Platen <[email protected]>
1 parent 886575e commit 17f9aed

File tree

7 files changed

+819
-0
lines changed

7 files changed

+819
-0
lines changed

docs/source/en/_toctree.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@
252252
title: Euler scheduler
253253
- local: api/schedulers/heun
254254
title: Heun Scheduler
255+
- local: api/schedulers/multistep_dpm_solver_inverse
256+
title: Inverse Multistep DPM-Solver
255257
- local: api/schedulers/ipndm
256258
title: IPNDM
257259
- local: api/schedulers/lms_discrete
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!--Copyright 2023 The HuggingFace Team. All rights reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
4+
the License. You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
9+
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
10+
specific language governing permissions and limitations under the License.
11+
-->
12+
13+
# Inverse Multistep DPM-Solver (DPMSolverMultistepInverse)
14+
15+
## Overview
16+
17+
This scheduler is the inverted scheduler of [DPM-Solver: A Fast ODE Solver for Diffusion Probabilistic Model Sampling in Around 10 Steps](https://arxiv.org/abs/2206.00927) and [DPM-Solver++: Fast Solver for Guided Sampling of Diffusion Probabilistic Models
18+
](https://arxiv.org/abs/2211.01095) by Cheng Lu, Yuhao Zhou, Fan Bao, Jianfei Chen, Chongxuan Li, and Jun Zhu.
19+
The implementation is mostly based on the DDIM inversion definition of [Null-text Inversion for Editing Real Images using Guided Diffusion Models](https://arxiv.org/pdf/2211.09794.pdf) and the ad-hoc notebook implementation for DiffEdit latent inversion [here](https://github.com/Xiang-cd/DiffEdit-stable-diffusion/blob/main/diffedit.ipynb).
20+
21+
## DPMSolverMultistepInverseScheduler
22+
[[autodoc]] DPMSolverMultistepInverseScheduler

src/diffusers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
DDIMScheduler,
7777
DDPMScheduler,
7878
DEISMultistepScheduler,
79+
DPMSolverMultistepInverseScheduler,
7980
DPMSolverMultistepScheduler,
8081
DPMSolverSinglestepScheduler,
8182
EulerAncestralDiscreteScheduler,

src/diffusers/schedulers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from .scheduling_ddpm import DDPMScheduler
3434
from .scheduling_deis_multistep import DEISMultistepScheduler
3535
from .scheduling_dpmsolver_multistep import DPMSolverMultistepScheduler
36+
from .scheduling_dpmsolver_multistep_inverse import DPMSolverMultistepInverseScheduler
3637
from .scheduling_dpmsolver_singlestep import DPMSolverSinglestepScheduler
3738
from .scheduling_euler_ancestral_discrete import EulerAncestralDiscreteScheduler
3839
from .scheduling_euler_discrete import EulerDiscreteScheduler

src/diffusers/schedulers/scheduling_dpmsolver_multistep_inverse.py

Lines changed: 701 additions & 0 deletions
Large diffs are not rendered by default.

src/diffusers/utils/dummy_pt_objects.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,21 @@ def from_pretrained(cls, *args, **kwargs):
450450
requires_backends(cls, ["torch"])
451451

452452

453+
class DPMSolverMultistepInverseScheduler(metaclass=DummyObject):
454+
_backends = ["torch"]
455+
456+
def __init__(self, *args, **kwargs):
457+
requires_backends(self, ["torch"])
458+
459+
@classmethod
460+
def from_config(cls, *args, **kwargs):
461+
requires_backends(cls, ["torch"])
462+
463+
@classmethod
464+
def from_pretrained(cls, *args, **kwargs):
465+
requires_backends(cls, ["torch"])
466+
467+
453468
class DPMSolverMultistepScheduler(metaclass=DummyObject):
454469
_backends = ["torch"]
455470

tests/pipelines/stable_diffusion_2/test_stable_diffusion_diffedit.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
AutoencoderKL,
2828
DDIMInverseScheduler,
2929
DDIMScheduler,
30+
DPMSolverMultistepInverseScheduler,
31+
DPMSolverMultistepScheduler,
3032
StableDiffusionDiffEditPipeline,
3133
UNet2DConditionModel,
3234
)
@@ -256,6 +258,30 @@ def test_inversion(self):
256258
def test_inference_batch_single_identical(self):
257259
super().test_inference_batch_single_identical(expected_max_diff=5e-3)
258260

261+
def test_inversion_dpm(self):
262+
device = "cpu"
263+
264+
components = self.get_dummy_components()
265+
266+
scheduler_args = {"beta_start": 0.00085, "beta_end": 0.012, "beta_schedule": "scaled_linear"}
267+
components["scheduler"] = DPMSolverMultistepScheduler(**scheduler_args)
268+
components["inverse_scheduler"] = DPMSolverMultistepInverseScheduler(**scheduler_args)
269+
270+
pipe = self.pipeline_class(**components)
271+
pipe.to(device)
272+
pipe.set_progress_bar_config(disable=None)
273+
274+
inputs = self.get_dummy_inversion_inputs(device)
275+
image = pipe.invert(**inputs).images
276+
image_slice = image[0, -1, -3:, -3:]
277+
278+
self.assertEqual(image.shape, (2, 32, 32, 3))
279+
expected_slice = np.array(
280+
[0.5150, 0.5134, 0.5043, 0.5376, 0.4694, 0.51050, 0.5015, 0.4407, 0.4799],
281+
)
282+
max_diff = np.abs(image_slice.flatten() - expected_slice).max()
283+
self.assertLessEqual(max_diff, 1e-3)
284+
259285

260286
@require_torch_gpu
261287
@slow
@@ -320,3 +346,54 @@ def test_stable_diffusion_diffedit_full(self):
320346
/ 255
321347
)
322348
assert np.abs((expected_image - image).max()) < 5e-1
349+
350+
def test_stable_diffusion_diffedit_dpm(self):
351+
generator = torch.manual_seed(0)
352+
353+
pipe = StableDiffusionDiffEditPipeline.from_pretrained(
354+
"stabilityai/stable-diffusion-2-1", safety_checker=None, torch_dtype=torch.float16
355+
)
356+
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
357+
pipe.inverse_scheduler = DPMSolverMultistepInverseScheduler.from_config(pipe.scheduler.config)
358+
pipe.enable_model_cpu_offload()
359+
pipe.set_progress_bar_config(disable=None)
360+
361+
source_prompt = "a bowl of fruit"
362+
target_prompt = "a bowl of pears"
363+
364+
mask_image = pipe.generate_mask(
365+
image=self.raw_image,
366+
source_prompt=source_prompt,
367+
target_prompt=target_prompt,
368+
generator=generator,
369+
)
370+
371+
inv_latents = pipe.invert(
372+
prompt=source_prompt,
373+
image=self.raw_image,
374+
inpaint_strength=0.7,
375+
generator=generator,
376+
num_inference_steps=25,
377+
).latents
378+
379+
image = pipe(
380+
prompt=target_prompt,
381+
mask_image=mask_image,
382+
image_latents=inv_latents,
383+
generator=generator,
384+
negative_prompt=source_prompt,
385+
inpaint_strength=0.7,
386+
num_inference_steps=25,
387+
output_type="numpy",
388+
).images[0]
389+
390+
expected_image = (
391+
np.array(
392+
load_image(
393+
"https://huggingface.co/datasets/hf-internal-testing/diffusers-images/resolve/main"
394+
"/diffedit/pears.png"
395+
).resize((768, 768))
396+
)
397+
/ 255
398+
)
399+
assert np.abs((expected_image - image).max()) < 5e-1

0 commit comments

Comments
 (0)