Skip to content

Commit 0057bf3

Browse files
committed
refactor cogvideox video to video pipeline tests to the new mixin structure
1 parent af95be6 commit 0057bf3

1 file changed

Lines changed: 40 additions & 157 deletions

File tree

tests/pipelines/cogvideo/test_cogvideox_video2video.py

Lines changed: 40 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import inspect
16-
import unittest
1715

1816
import numpy as np
1917
import torch
@@ -22,36 +20,25 @@
2220

2321
from diffusers import AutoencoderKLCogVideoX, CogVideoXTransformer3DModel, CogVideoXVideoToVideoPipeline, DDIMScheduler
2422

25-
from ...testing_utils import enable_full_determinism, torch_device
26-
from ..pipeline_params import TEXT_TO_IMAGE_BATCH_PARAMS, TEXT_TO_IMAGE_IMAGE_PARAMS, TEXT_TO_IMAGE_PARAMS
27-
from ..test_pipelines_common import (
23+
from ..testing_utils import (
24+
BasePipelineTesterConfig,
25+
MemoryTesterMixin,
2826
PipelineTesterMixin,
2927
check_qkv_fusion_matches_attn_procs_length,
3028
check_qkv_fusion_processors_exist,
31-
to_np,
3229
)
3330

3431

35-
enable_full_determinism()
36-
37-
38-
class CogVideoXVideoToVideoPipelineFastTests(PipelineTesterMixin, unittest.TestCase):
32+
class CogVideoXVideoToVideoPipelineTesterConfig(BasePipelineTesterConfig):
3933
pipeline_class = CogVideoXVideoToVideoPipeline
40-
params = TEXT_TO_IMAGE_PARAMS - {"cross_attention_kwargs"}
41-
batch_params = TEXT_TO_IMAGE_BATCH_PARAMS.union({"video"})
42-
image_params = TEXT_TO_IMAGE_IMAGE_PARAMS
43-
image_latents_params = TEXT_TO_IMAGE_IMAGE_PARAMS
44-
required_optional_params = frozenset(
45-
[
46-
"num_inference_steps",
47-
"generator",
48-
"latents",
49-
"return_dict",
50-
"callback_on_step_end",
51-
"callback_on_step_end_tensor_inputs",
52-
]
34+
required_input_params_in_call_signature = frozenset(
35+
["prompt", "negative_prompt", "height", "width", "guidance_scale", "prompt_embeds", "negative_prompt_embeds"]
36+
)
37+
batch_input_params = frozenset(["prompt", "video"])
38+
# CogVideoX is a video pipeline: it exposes `num_videos_per_prompt`, not the base default `num_images_per_prompt`.
39+
optional_input_params = frozenset(
40+
["num_inference_steps", "num_videos_per_prompt", "generator", "latents", "output_type", "return_dict"]
5341
)
54-
test_xformers_attention = False
5542

5643
def get_dummy_components(self):
5744
torch.manual_seed(0)
@@ -112,21 +99,16 @@ def get_dummy_components(self):
11299
}
113100
return components
114101

115-
def get_dummy_inputs(self, device, seed: int = 0, num_frames: int = 8):
116-
if str(device).startswith("mps"):
117-
generator = torch.manual_seed(seed)
118-
else:
119-
generator = torch.Generator(device=device).manual_seed(seed)
120-
102+
def get_dummy_inputs(self):
121103
video_height = 16
122104
video_width = 16
123-
video = [Image.new("RGB", (video_width, video_height))] * num_frames
105+
video = [Image.new("RGB", (video_width, video_height))] * 8
124106

125107
inputs = {
126108
"video": video,
127109
"prompt": "dance monkey",
128110
"negative_prompt": "",
129-
"generator": generator,
111+
"generator": self.get_generator(0),
130112
"num_inference_steps": 2,
131113
"strength": 0.5,
132114
"guidance_scale": 6.0,
@@ -138,135 +120,38 @@ def get_dummy_inputs(self, device, seed: int = 0, num_frames: int = 8):
138120
}
139121
return inputs
140122

141-
def test_inference(self):
142-
device = "cpu"
143123

144-
components = self.get_dummy_components()
145-
pipe = self.pipeline_class(**components)
146-
pipe.to(device)
147-
pipe.set_progress_bar_config(disable=None)
124+
class TestCogVideoXVideoToVideoPipeline(CogVideoXVideoToVideoPipelineTesterConfig, PipelineTesterMixin):
125+
def test_inference(self):
126+
# Run on CPU: the expected slice below is CPU-specific.
127+
pipe = self.get_pipeline()
148128

149-
inputs = self.get_dummy_inputs(device)
129+
inputs = self.get_dummy_inputs()
150130
video = pipe(**inputs).frames
151131
generated_video = video[0]
132+
assert generated_video.shape == (8, 3, 16, 16)
152133

153-
self.assertEqual(generated_video.shape, (8, 3, 16, 16))
154-
expected_video = torch.randn(8, 3, 16, 16)
155-
max_diff = np.abs(generated_video - expected_video).max()
156-
self.assertLessEqual(max_diff, 1e10)
157-
158-
def test_callback_inputs(self):
159-
sig = inspect.signature(self.pipeline_class.__call__)
160-
has_callback_tensor_inputs = "callback_on_step_end_tensor_inputs" in sig.parameters
161-
has_callback_step_end = "callback_on_step_end" in sig.parameters
162-
163-
if not (has_callback_tensor_inputs and has_callback_step_end):
164-
return
165-
166-
components = self.get_dummy_components()
167-
pipe = self.pipeline_class(**components)
168-
pipe = pipe.to(torch_device)
169-
pipe.set_progress_bar_config(disable=None)
170-
self.assertTrue(
171-
hasattr(pipe, "_callback_tensor_inputs"),
172-
f" {self.pipeline_class} should have `_callback_tensor_inputs` that defines a list of tensor variables its callback function can use as inputs",
173-
)
174-
175-
def callback_inputs_subset(pipe, i, t, callback_kwargs):
176-
# iterate over callback args
177-
for tensor_name, tensor_value in callback_kwargs.items():
178-
# check that we're only passing in allowed tensor inputs
179-
assert tensor_name in pipe._callback_tensor_inputs
180-
181-
return callback_kwargs
182-
183-
def callback_inputs_all(pipe, i, t, callback_kwargs):
184-
for tensor_name in pipe._callback_tensor_inputs:
185-
assert tensor_name in callback_kwargs
186-
187-
# iterate over callback args
188-
for tensor_name, tensor_value in callback_kwargs.items():
189-
# check that we're only passing in allowed tensor inputs
190-
assert tensor_name in pipe._callback_tensor_inputs
134+
# fmt: off
135+
expected_slice = torch.tensor([0.5644, 0.6029, 0.6017, 0.5937, 0.5991, 0.5907, 0.6141, 0.5340, 0.3184, 0.4219, 0.4406, 0.4330, 0.4692, 0.4547, 0.4562, 0.5092])
136+
# fmt: on
191137

192-
return callback_kwargs
193-
194-
inputs = self.get_dummy_inputs(torch_device)
195-
196-
# Test passing in a subset
197-
inputs["callback_on_step_end"] = callback_inputs_subset
198-
inputs["callback_on_step_end_tensor_inputs"] = ["latents"]
199-
output = pipe(**inputs)[0]
200-
201-
# Test passing in a everything
202-
inputs["callback_on_step_end"] = callback_inputs_all
203-
inputs["callback_on_step_end_tensor_inputs"] = pipe._callback_tensor_inputs
204-
output = pipe(**inputs)[0]
205-
206-
def callback_inputs_change_tensor(pipe, i, t, callback_kwargs):
207-
is_last = i == (pipe.num_timesteps - 1)
208-
if is_last:
209-
callback_kwargs["latents"] = torch.zeros_like(callback_kwargs["latents"])
210-
return callback_kwargs
211-
212-
inputs["callback_on_step_end"] = callback_inputs_change_tensor
213-
inputs["callback_on_step_end_tensor_inputs"] = pipe._callback_tensor_inputs
214-
output = pipe(**inputs)[0]
215-
assert output.abs().sum() < 1e10
138+
generated_slice = generated_video.flatten()
139+
generated_slice = torch.cat([generated_slice[:8], generated_slice[-8:]])
140+
assert torch.allclose(generated_slice, expected_slice, atol=1e-3)
216141

217142
def test_inference_batch_single_identical(self):
218-
self._test_inference_batch_single_identical(batch_size=3, expected_max_diff=1e-3)
219-
220-
def test_attention_slicing_forward_pass(
221-
self, test_max_difference=True, test_mean_pixel_difference=True, expected_max_diff=1e-3
222-
):
223-
if not self.test_attention_slicing:
224-
return
225-
226-
components = self.get_dummy_components()
227-
pipe = self.pipeline_class(**components)
228-
for component in pipe.components.values():
229-
if hasattr(component, "set_default_attn_processor"):
230-
component.set_default_attn_processor()
231-
pipe.to(torch_device)
232-
pipe.set_progress_bar_config(disable=None)
233-
234-
generator_device = "cpu"
235-
inputs = self.get_dummy_inputs(generator_device)
236-
output_without_slicing = pipe(**inputs)[0]
237-
238-
pipe.enable_attention_slicing(slice_size=1)
239-
inputs = self.get_dummy_inputs(generator_device)
240-
output_with_slicing1 = pipe(**inputs)[0]
241-
242-
pipe.enable_attention_slicing(slice_size=2)
243-
inputs = self.get_dummy_inputs(generator_device)
244-
output_with_slicing2 = pipe(**inputs)[0]
245-
246-
if test_max_difference:
247-
max_diff1 = np.abs(to_np(output_with_slicing1) - to_np(output_without_slicing)).max()
248-
max_diff2 = np.abs(to_np(output_with_slicing2) - to_np(output_without_slicing)).max()
249-
self.assertLess(
250-
max(max_diff1, max_diff2),
251-
expected_max_diff,
252-
"Attention slicing should not affect the inference results",
253-
)
143+
super().test_inference_batch_single_identical(batch_size=3, expected_max_diff=1e-3)
254144

255145
def test_vae_tiling(self, expected_diff_max: float = 0.2):
256146
# Since VideoToVideo uses both encoder and decoder tiling, there seems to be much more numerical
257147
# difference. We seem to need a higher tolerance here...
258148
# TODO(aryan): Look into this more deeply
259149
expected_diff_max = 0.4
260150

261-
generator_device = "cpu"
262-
components = self.get_dummy_components()
263-
264-
pipe = self.pipeline_class(**components)
265-
pipe.to("cpu")
266-
pipe.set_progress_bar_config(disable=None)
151+
pipe = self.get_pipeline()
267152

268153
# Without tiling
269-
inputs = self.get_dummy_inputs(generator_device)
154+
inputs = self.get_dummy_inputs()
270155
inputs["height"] = inputs["width"] = 128
271156
output_without_tiling = pipe(**inputs)[0]
272157

@@ -277,24 +162,18 @@ def test_vae_tiling(self, expected_diff_max: float = 0.2):
277162
tile_overlap_factor_height=1 / 12,
278163
tile_overlap_factor_width=1 / 12,
279164
)
280-
inputs = self.get_dummy_inputs(generator_device)
165+
inputs = self.get_dummy_inputs()
281166
inputs["height"] = inputs["width"] = 128
282167
output_with_tiling = pipe(**inputs)[0]
283168

284-
self.assertLess(
285-
(to_np(output_without_tiling) - to_np(output_with_tiling)).max(),
286-
expected_diff_max,
287-
"VAE tiling should not affect the inference results",
169+
assert (output_without_tiling - output_with_tiling).max() < expected_diff_max, (
170+
"VAE tiling should not affect the inference results"
288171
)
289172

290173
def test_fused_qkv_projections(self):
291-
device = "cpu" # ensure determinism for the device-dependent torch.Generator
292-
components = self.get_dummy_components()
293-
pipe = self.pipeline_class(**components)
294-
pipe = pipe.to(device)
295-
pipe.set_progress_bar_config(disable=None)
174+
pipe = self.get_pipeline()
296175

297-
inputs = self.get_dummy_inputs(device)
176+
inputs = self.get_dummy_inputs()
298177
frames = pipe(**inputs).frames # [B, F, C, H, W]
299178
original_image_slice = frames[0, -2:, -1, -3:, -3:]
300179

@@ -306,12 +185,12 @@ def test_fused_qkv_projections(self):
306185
pipe.transformer, pipe.transformer.original_attn_processors
307186
), "Something wrong with the attention processors concerning the fused QKV projections."
308187

309-
inputs = self.get_dummy_inputs(device)
188+
inputs = self.get_dummy_inputs()
310189
frames = pipe(**inputs).frames
311190
image_slice_fused = frames[0, -2:, -1, -3:, -3:]
312191

313192
pipe.transformer.unfuse_qkv_projections()
314-
inputs = self.get_dummy_inputs(device)
193+
inputs = self.get_dummy_inputs()
315194
frames = pipe(**inputs).frames
316195
image_slice_disabled = frames[0, -2:, -1, -3:, -3:]
317196

@@ -324,3 +203,7 @@ def test_fused_qkv_projections(self):
324203
assert np.allclose(original_image_slice, image_slice_disabled, atol=1e-2, rtol=1e-2), (
325204
"Original outputs should match when fused QKV projections are disabled."
326205
)
206+
207+
208+
class TestCogVideoXVideoToVideoPipelineMemory(CogVideoXVideoToVideoPipelineTesterConfig, MemoryTesterMixin):
209+
pass

0 commit comments

Comments
 (0)