Skip to content

Commit ee9f497

Browse files
refactor deepfloyd if superresolution pipeline tests to the new mixin structure (#14603)
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
1 parent ef0436f commit ee9f497

1 file changed

Lines changed: 153 additions & 51 deletions

File tree

tests/pipelines/deepfloyd_if/test_if_superresolution.py

Lines changed: 153 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -15,101 +15,203 @@
1515

1616
import gc
1717
import random
18-
import unittest
1918

19+
import pytest
2020
import torch
21+
from transformers import AutoConfig, AutoTokenizer, T5EncoderModel
2122

22-
from diffusers import IFSuperResolutionPipeline
23+
from diffusers import DDPMScheduler, IFSuperResolutionPipeline, UNet2DConditionModel
2324
from diffusers.models.attention_processor import AttnAddedKVProcessor
24-
from diffusers.utils.import_utils import is_xformers_available
25+
from diffusers.pipelines.deepfloyd_if import IFWatermarker
2526

2627
from ...testing_utils import (
28+
assert_tensors_close,
2729
backend_empty_cache,
2830
backend_max_memory_allocated,
2931
backend_reset_max_memory_allocated,
3032
backend_reset_peak_memory_stats,
3133
floats_tensor,
3234
load_numpy,
33-
require_accelerator,
3435
require_torch_accelerator,
3536
skip_mps,
3637
slow,
3738
torch_device,
3839
)
39-
from ..pipeline_params import TEXT_GUIDED_IMAGE_VARIATION_BATCH_PARAMS, TEXT_GUIDED_IMAGE_VARIATION_PARAMS
40-
from ..test_pipelines_common import PipelineTesterMixin, assert_mean_pixel_difference
41-
from . import IFPipelineTesterMixin
40+
from ..test_pipelines_common import assert_mean_pixel_difference
41+
from ..testing_utils import (
42+
BasePipelineTesterConfig,
43+
PipelineOffloadTesterMixin,
44+
PipelineTesterMixin,
45+
)
4246

4347

44-
@skip_mps
45-
class IFSuperResolutionPipelineFastTests(PipelineTesterMixin, IFPipelineTesterMixin, unittest.TestCase):
48+
class IFSuperResolutionPipelineTesterConfig(BasePipelineTesterConfig):
4649
pipeline_class = IFSuperResolutionPipeline
47-
params = TEXT_GUIDED_IMAGE_VARIATION_PARAMS - {"width", "height"}
48-
batch_params = TEXT_GUIDED_IMAGE_VARIATION_BATCH_PARAMS
49-
required_optional_params = PipelineTesterMixin.required_optional_params - {"latents"}
50+
required_input_params_in_call_signature = frozenset(
51+
["prompt", "image", "guidance_scale", "negative_prompt", "prompt_embeds", "negative_prompt_embeds"]
52+
)
53+
# IF pipelines take no `latents` argument (pixel-space UNet, no user-suppliable latents)
54+
optional_input_params = BasePipelineTesterConfig.optional_input_params - {"latents"}
55+
batch_input_params = frozenset(["prompt", "image", "negative_prompt"])
56+
output_shape = (3, 32, 32)
5057

5158
def get_dummy_components(self):
52-
return self._get_superresolution_dummy_components()
59+
torch.manual_seed(0)
60+
config = AutoConfig.from_pretrained("hf-internal-testing/tiny-random-t5")
61+
text_encoder = T5EncoderModel(config)
62+
63+
torch.manual_seed(0)
64+
tokenizer = AutoTokenizer.from_pretrained("hf-internal-testing/tiny-random-t5")
65+
66+
torch.manual_seed(0)
67+
unet = UNet2DConditionModel(
68+
sample_size=32,
69+
layers_per_block=[1, 2],
70+
block_out_channels=[32, 64],
71+
down_block_types=[
72+
"ResnetDownsampleBlock2D",
73+
"SimpleCrossAttnDownBlock2D",
74+
],
75+
mid_block_type="UNetMidBlock2DSimpleCrossAttn",
76+
up_block_types=["SimpleCrossAttnUpBlock2D", "ResnetUpsampleBlock2D"],
77+
in_channels=6,
78+
out_channels=6,
79+
cross_attention_dim=32,
80+
encoder_hid_dim=32,
81+
attention_head_dim=8,
82+
addition_embed_type="text",
83+
addition_embed_type_num_heads=2,
84+
cross_attention_norm="group_norm",
85+
resnet_time_scale_shift="scale_shift",
86+
act_fn="gelu",
87+
class_embed_type="timestep",
88+
mid_block_scale_factor=1.414,
89+
time_embedding_act_fn="gelu",
90+
time_embedding_dim=32,
91+
)
92+
unet.set_attn_processor(AttnAddedKVProcessor()) # For reproducibility tests
93+
94+
torch.manual_seed(0)
95+
scheduler = DDPMScheduler(
96+
num_train_timesteps=1000,
97+
beta_schedule="squaredcos_cap_v2",
98+
beta_start=0.0001,
99+
beta_end=0.02,
100+
thresholding=True,
101+
dynamic_thresholding_ratio=0.95,
102+
sample_max_value=1.0,
103+
prediction_type="epsilon",
104+
variance_type="learned_range",
105+
)
53106

54-
def get_dummy_inputs(self, device, seed=0):
55-
if str(device).startswith("mps"):
56-
generator = torch.manual_seed(seed)
57-
else:
58-
generator = torch.Generator(device=device).manual_seed(seed)
107+
torch.manual_seed(0)
108+
image_noising_scheduler = DDPMScheduler(
109+
num_train_timesteps=1000,
110+
beta_schedule="squaredcos_cap_v2",
111+
beta_start=0.0001,
112+
beta_end=0.02,
113+
)
59114

60-
image = floats_tensor((1, 3, 32, 32), rng=random.Random(seed)).to(device)
115+
torch.manual_seed(0)
116+
watermarker = IFWatermarker()
117+
118+
return {
119+
"text_encoder": text_encoder,
120+
"tokenizer": tokenizer,
121+
"unet": unet,
122+
"scheduler": scheduler,
123+
"image_noising_scheduler": image_noising_scheduler,
124+
"watermarker": watermarker,
125+
"safety_checker": None,
126+
"feature_extractor": None,
127+
}
61128

62-
inputs = {
129+
def get_dummy_inputs(self):
130+
image = floats_tensor((1, 3, 32, 32), rng=random.Random(0)).to(torch_device)
131+
return {
63132
"prompt": "A painting of a squirrel eating a burger",
64133
"image": image,
65-
"generator": generator,
134+
"generator": self.get_generator(0),
66135
"num_inference_steps": 2,
67-
"output_type": "np",
136+
# Request torch outputs so tests compare torch tensors directly (see `BasePipelineTesterConfig`).
137+
"output_type": "pt",
68138
}
69139

70-
return inputs
71140

72-
@unittest.skipIf(
73-
torch_device != "cuda" or not is_xformers_available(),
74-
reason="XFormers attention is only available with CUDA and `xformers` installed",
75-
)
76-
def test_xformers_attention_forwardGenerator_pass(self):
77-
self._test_xformers_attention_forwardGenerator_pass(expected_max_diff=1e-3)
141+
@skip_mps
142+
class TestIFSuperResolutionPipeline(IFSuperResolutionPipelineTesterConfig, PipelineTesterMixin):
143+
def test_inference(self):
144+
# Run on CPU: the expected slice below is CPU-specific.
145+
pipe = self.get_pipeline()
78146

79-
@unittest.skipIf(torch_device not in ["cuda", "xpu"], reason="float16 requires CUDA or XPU")
80-
@require_accelerator
81-
def test_save_load_float16(self):
82-
# Due to non-determinism in save load of the hf-internal-testing/tiny-random-t5 text encoder
83-
super().test_save_load_float16(expected_max_diff=1e-1)
147+
inputs = self.get_dummy_inputs()
148+
image = pipe(**inputs).images
149+
generated_image = image[0]
150+
assert generated_image.shape == self.output_shape
84151

85-
def test_attention_slicing_forward_pass(self):
86-
self._test_attention_slicing_forward_pass(expected_max_diff=1e-2)
152+
# fmt: off
153+
expected_slice = torch.tensor([-0.9507, -0.8897, -0.2251, -0.8282, 0.9614, 1.0000, 0.2031, -0.9011, 1.0000, -0.9383, -0.3545, 0.9513, -1.0000, -1.0000, -1.0000, -0.9787])
154+
# fmt: on
87155

88-
def test_save_load_local(self):
89-
self._test_save_load_local()
156+
generated_slice = generated_image.flatten()
157+
generated_slice = torch.cat([generated_slice[:8], generated_slice[-8:]])
158+
assert_tensors_close(generated_slice, expected_slice, atol=1e-3)
90159

91160
def test_inference_batch_single_identical(self):
92-
self._test_inference_batch_single_identical(
93-
expected_max_diff=1e-2,
161+
super().test_inference_batch_single_identical(expected_max_diff=1e-2)
162+
163+
def test_save_load_optional_components(self, tmp_path):
164+
# The text encoder is optional so a pre-encoded prompt can be passed directly; the base test would
165+
# pass the raw prompt with `text_encoder=None`, so encode it first (the intended usage).
166+
pipe = self.get_pipeline().to(torch_device)
167+
168+
inputs = self.get_dummy_inputs()
169+
prompt = inputs.pop("prompt")
170+
prompt_embeds, negative_prompt_embeds = pipe.encode_prompt(prompt)
171+
172+
for optional_component in pipe._optional_components:
173+
setattr(pipe, optional_component, None)
174+
175+
inputs["prompt_embeds"] = prompt_embeds
176+
inputs["negative_prompt_embeds"] = negative_prompt_embeds
177+
torch.manual_seed(0)
178+
output = pipe(**inputs)[0]
179+
180+
pipe.save_pretrained(tmp_path, safe_serialization=False)
181+
pipe_loaded = self.pipeline_class.from_pretrained(tmp_path)
182+
pipe_loaded.to(torch_device)
183+
pipe_loaded.set_progress_bar_config(disable=None)
184+
185+
for optional_component in pipe._optional_components:
186+
assert getattr(pipe_loaded, optional_component) is None, (
187+
f"`{optional_component}` did not stay set to None after loading."
188+
)
189+
190+
inputs = self.get_dummy_inputs()
191+
inputs.pop("prompt")
192+
inputs["prompt_embeds"] = prompt_embeds
193+
inputs["negative_prompt_embeds"] = negative_prompt_embeds
194+
torch.manual_seed(0)
195+
output_loaded = pipe_loaded(**inputs)[0]
196+
197+
assert_tensors_close(
198+
output_loaded, output, atol=1e-4, msg="Output changed after dropping optional components."
94199
)
95200

96-
@unittest.skip("Test done elsewhere.")
97-
def test_save_load_optional_components(self, expected_max_difference=0.0001):
98-
pass
201+
202+
@skip_mps
203+
class TestIFSuperResolutionPipelineMemory(IFSuperResolutionPipelineTesterConfig, PipelineOffloadTesterMixin):
204+
pass
99205

100206

101207
@slow
102208
@require_torch_accelerator
103-
class IFSuperResolutionPipelineSlowTests(unittest.TestCase):
104-
def setUp(self):
105-
# clean up the VRAM before each test
106-
super().setUp()
209+
class TestIFSuperResolutionPipelineSlow:
210+
@pytest.fixture(autouse=True)
211+
def cleanup(self):
107212
gc.collect()
108213
backend_empty_cache(torch_device)
109-
110-
def tearDown(self):
111-
# clean up the VRAM after each test
112-
super().tearDown()
214+
yield
113215
gc.collect()
114216
backend_empty_cache(torch_device)
115217

0 commit comments

Comments
 (0)