From 7fc630b93d44aac497825a976bcb629f683d33b2 Mon Sep 17 00:00:00 2001 From: Akshan Krithick Date: Fri, 17 Jul 2026 13:19:28 -0700 Subject: [PATCH 1/5] refactor qwenimage pipeline tests to the new mixin structure --- tests/pipelines/qwenimage/test_qwenimage.py | 171 +++++++------------- 1 file changed, 56 insertions(+), 115 deletions(-) diff --git a/tests/pipelines/qwenimage/test_qwenimage.py b/tests/pipelines/qwenimage/test_qwenimage.py index 4fc76ca624b6..e96a84e4ba17 100644 --- a/tests/pipelines/qwenimage/test_qwenimage.py +++ b/tests/pipelines/qwenimage/test_qwenimage.py @@ -12,9 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest - -import numpy as np import torch from transformers import Qwen2_5_VLConfig, Qwen2_5_VLForConditionalGeneration, Qwen2Tokenizer @@ -25,33 +22,25 @@ QwenImageTransformer2DModel, ) -from ...testing_utils import enable_full_determinism, torch_device -from ..pipeline_params import TEXT_TO_IMAGE_BATCH_PARAMS, TEXT_TO_IMAGE_IMAGE_PARAMS, TEXT_TO_IMAGE_PARAMS -from ..test_pipelines_common import PipelineTesterMixin, to_np - - -enable_full_determinism() +from ...testing_utils import torch_device +from ..testing_utils import ( + BasePipelineTesterConfig, + FasterCacheTesterMixin, + FirstBlockCacheTesterMixin, + MagCacheTesterMixin, + MemoryTesterMixin, + PipelineTesterMixin, + PyramidAttentionBroadcastTesterMixin, + TaylorSeerCacheTesterMixin, +) -class QwenImagePipelineFastTests(PipelineTesterMixin, unittest.TestCase): +class QwenImagePipelineTesterConfig(BasePipelineTesterConfig): pipeline_class = QwenImagePipeline - params = TEXT_TO_IMAGE_PARAMS - {"cross_attention_kwargs"} - batch_params = TEXT_TO_IMAGE_BATCH_PARAMS - image_params = TEXT_TO_IMAGE_IMAGE_PARAMS - image_latents_params = TEXT_TO_IMAGE_IMAGE_PARAMS - required_optional_params = frozenset( - [ - "num_inference_steps", - "generator", - "latents", - "return_dict", - "callback_on_step_end", - "callback_on_step_end_tensor_inputs", - ] + required_input_params_in_call_signature = frozenset( + ["prompt", "negative_prompt", "true_cfg_scale", "height", "width", "guidance_scale", "prompt_embeds"] ) - test_xformers_attention = False - test_layerwise_casting = True - test_group_offloading = True + batch_input_params = frozenset(["prompt"]) def get_dummy_components(self): torch.manual_seed(0) @@ -75,10 +64,8 @@ def get_dummy_components(self): dim_mult=[1, 2, 4], num_res_blocks=1, temperal_downsample=[False, True], - # fmt: off latents_mean=[0.0] * 4, latents_std=[1.0] * 4, - # fmt: on ) torch.manual_seed(0) @@ -115,134 +102,62 @@ def get_dummy_components(self): text_encoder = Qwen2_5_VLForConditionalGeneration(config).eval() tokenizer = Qwen2Tokenizer.from_pretrained("hf-internal-testing/tiny-random-Qwen2VLForConditionalGeneration") - components = { + return { "transformer": transformer, "vae": vae, "scheduler": scheduler, "text_encoder": text_encoder, "tokenizer": tokenizer, } - return components - def get_dummy_inputs(self, device, seed=0): - if str(device).startswith("mps"): - generator = torch.manual_seed(seed) - else: - generator = torch.Generator(device=device).manual_seed(seed) - - inputs = { + def get_dummy_inputs(self): + return { "prompt": "dance monkey", "negative_prompt": "bad quality", - "generator": generator, + "generator": self.get_generator(0), "num_inference_steps": 2, "guidance_scale": 3.0, "true_cfg_scale": 1.0, "height": 32, "width": 32, "max_sequence_length": 16, + # Request torch outputs so tests compare torch tensors directly (see `BasePipelineTesterConfig`). "output_type": "pt", } - return inputs - - def test_inference(self): - device = "cpu" - - components = self.get_dummy_components() - pipe = self.pipeline_class(**components) - pipe.to(device) - pipe.set_progress_bar_config(disable=None) - - inputs = self.get_dummy_inputs(device) - image = pipe(**inputs).images - generated_image = image[0] - self.assertEqual(generated_image.shape, (3, 32, 32)) - - # fmt: off - expected_slice = torch.tensor([0.5633, 0.6368, 0.6015, 0.5637, 0.5817, 0.5528, 0.5718, 0.6326, 0.4147, 0.3556, 0.5623, 0.4833, 0.4971, 0.5262, 0.4087, 0.5021]) - # fmt: on - - generated_slice = generated_image.flatten() - generated_slice = torch.cat([generated_slice[:8], generated_slice[-8:]]) - self.assertTrue(torch.allclose(generated_slice, expected_slice, atol=5e-3)) +class TestQwenImagePipeline(QwenImagePipelineTesterConfig, PipelineTesterMixin): def test_inference_batch_single_identical(self): - self._test_inference_batch_single_identical(batch_size=3, expected_max_diff=1e-1) - - def test_attention_slicing_forward_pass( - self, test_max_difference=True, test_mean_pixel_difference=True, expected_max_diff=1e-3 - ): - if not self.test_attention_slicing: - return - - components = self.get_dummy_components() - pipe = self.pipeline_class(**components) - for component in pipe.components.values(): - if hasattr(component, "set_default_attn_processor"): - component.set_default_attn_processor() - pipe.to(torch_device) - pipe.set_progress_bar_config(disable=None) - - generator_device = "cpu" - inputs = self.get_dummy_inputs(generator_device) - output_without_slicing = pipe(**inputs)[0] - - pipe.enable_attention_slicing(slice_size=1) - inputs = self.get_dummy_inputs(generator_device) - output_with_slicing1 = pipe(**inputs)[0] - - pipe.enable_attention_slicing(slice_size=2) - inputs = self.get_dummy_inputs(generator_device) - output_with_slicing2 = pipe(**inputs)[0] - - if test_max_difference: - max_diff1 = np.abs(to_np(output_with_slicing1) - to_np(output_without_slicing)).max() - max_diff2 = np.abs(to_np(output_with_slicing2) - to_np(output_without_slicing)).max() - self.assertLess( - max(max_diff1, max_diff2), - expected_max_diff, - "Attention slicing should not affect the inference results", - ) + super().test_inference_batch_single_identical(expected_max_diff=1e-1) def test_vae_tiling(self, expected_diff_max: float = 0.2): - generator_device = "cpu" - components = self.get_dummy_components() - - pipe = self.pipeline_class(**components) - pipe.to("cpu") + pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device) pipe.set_progress_bar_config(disable=None) - # Without tiling - inputs = self.get_dummy_inputs(generator_device) + inputs = self.get_dummy_inputs() inputs["height"] = inputs["width"] = 128 output_without_tiling = pipe(**inputs)[0] - # With tiling pipe.vae.enable_tiling( tile_sample_min_height=96, tile_sample_min_width=96, tile_sample_stride_height=64, tile_sample_stride_width=64, ) - inputs = self.get_dummy_inputs(generator_device) + inputs = self.get_dummy_inputs() inputs["height"] = inputs["width"] = 128 output_with_tiling = pipe(**inputs)[0] - self.assertLess( - (to_np(output_without_tiling) - to_np(output_with_tiling)).max(), - expected_diff_max, - "VAE tiling should not affect the inference results", + assert (output_without_tiling - output_with_tiling).abs().max() < expected_diff_max, ( + "VAE tiling should not affect the inference results." ) def test_true_cfg_without_negative_prompt_embeds_mask(self): - components = self.get_dummy_components() - pipe = self.pipeline_class(**components) - pipe.to(torch_device) + pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device) pipe.set_progress_bar_config(disable=None) - inputs = self.get_dummy_inputs(torch_device) + inputs = self.get_dummy_inputs() prompt = inputs.pop("prompt") - prompt_embeds, prompt_embeds_mask = pipe.encode_prompt( prompt=prompt, device=torch_device, @@ -258,4 +173,30 @@ def test_true_cfg_without_negative_prompt_embeds_mask(self): inputs["true_cfg_scale"] = 2.0 image = pipe(**inputs).images - self.assertIsNotNone(image) + assert image is not None + + +class TestQwenImagePipelineMemory(QwenImagePipelineTesterConfig, MemoryTesterMixin): + pass + + +class TestQwenImagePipelinePyramidAttentionBroadcast( + QwenImagePipelineTesterConfig, PyramidAttentionBroadcastTesterMixin +): + pass + + +class TestQwenImagePipelineFasterCache(QwenImagePipelineTesterConfig, FasterCacheTesterMixin): + pass + + +class TestQwenImagePipelineFirstBlockCache(QwenImagePipelineTesterConfig, FirstBlockCacheTesterMixin): + pass + + +class TestQwenImagePipelineTaylorSeerCache(QwenImagePipelineTesterConfig, TaylorSeerCacheTesterMixin): + pass + + +class TestQwenImagePipelineMagCache(QwenImagePipelineTesterConfig, MagCacheTesterMixin): + pass From 62831a1038ba7248e8d08d0c2f7ace2f422278c4 Mon Sep 17 00:00:00 2001 From: Akshan Krithick Date: Fri, 17 Jul 2026 18:20:26 -0700 Subject: [PATCH 2/5] parametrize num_layers in qwenimage test dummy components for cache mixins --- tests/pipelines/qwenimage/test_qwenimage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/pipelines/qwenimage/test_qwenimage.py b/tests/pipelines/qwenimage/test_qwenimage.py index e96a84e4ba17..1d2d9a4f8cb0 100644 --- a/tests/pipelines/qwenimage/test_qwenimage.py +++ b/tests/pipelines/qwenimage/test_qwenimage.py @@ -42,13 +42,13 @@ class QwenImagePipelineTesterConfig(BasePipelineTesterConfig): ) batch_input_params = frozenset(["prompt"]) - def get_dummy_components(self): + def get_dummy_components(self, num_layers: int = 2): torch.manual_seed(0) transformer = QwenImageTransformer2DModel( patch_size=2, in_channels=16, out_channels=4, - num_layers=2, + num_layers=num_layers, attention_head_dim=16, num_attention_heads=3, joint_attention_dim=16, From 8d37224679caf02f6144726246df4548fe654542 Mon Sep 17 00:00:00 2001 From: Akshan Krithick Date: Fri, 17 Jul 2026 18:35:57 -0700 Subject: [PATCH 3/5] drop faster cache test for qwenimage (unsupported by the model) --- tests/pipelines/qwenimage/test_qwenimage.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/pipelines/qwenimage/test_qwenimage.py b/tests/pipelines/qwenimage/test_qwenimage.py index 1d2d9a4f8cb0..03f1f577a123 100644 --- a/tests/pipelines/qwenimage/test_qwenimage.py +++ b/tests/pipelines/qwenimage/test_qwenimage.py @@ -25,7 +25,6 @@ from ...testing_utils import torch_device from ..testing_utils import ( BasePipelineTesterConfig, - FasterCacheTesterMixin, FirstBlockCacheTesterMixin, MagCacheTesterMixin, MemoryTesterMixin, @@ -186,10 +185,6 @@ class TestQwenImagePipelinePyramidAttentionBroadcast( pass -class TestQwenImagePipelineFasterCache(QwenImagePipelineTesterConfig, FasterCacheTesterMixin): - pass - - class TestQwenImagePipelineFirstBlockCache(QwenImagePipelineTesterConfig, FirstBlockCacheTesterMixin): pass From f7c640e44186b0dee24ada9e0d7fe97cd32009a5 Mon Sep 17 00:00:00 2001 From: Akshan Krithick Date: Fri, 17 Jul 2026 22:02:38 -0700 Subject: [PATCH 4/5] restore test_inference and drop caching tests for qwenimage --- tests/pipelines/qwenimage/test_qwenimage.py | 39 +++++++++------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/tests/pipelines/qwenimage/test_qwenimage.py b/tests/pipelines/qwenimage/test_qwenimage.py index 03f1f577a123..a66dbfbaa026 100644 --- a/tests/pipelines/qwenimage/test_qwenimage.py +++ b/tests/pipelines/qwenimage/test_qwenimage.py @@ -25,12 +25,8 @@ from ...testing_utils import torch_device from ..testing_utils import ( BasePipelineTesterConfig, - FirstBlockCacheTesterMixin, - MagCacheTesterMixin, MemoryTesterMixin, PipelineTesterMixin, - PyramidAttentionBroadcastTesterMixin, - TaylorSeerCacheTesterMixin, ) @@ -126,6 +122,23 @@ def get_dummy_inputs(self): class TestQwenImagePipeline(QwenImagePipelineTesterConfig, PipelineTesterMixin): + def test_inference(self): + # Run on CPU: the expected slice below is CPU-specific. + pipe = self.get_pipeline() + + inputs = self.get_dummy_inputs() + image = pipe(**inputs).images + generated_image = image[0] + assert generated_image.shape == (3, 32, 32) + + # fmt: off + expected_slice = torch.tensor([0.5633, 0.6368, 0.6015, 0.5637, 0.5817, 0.5528, 0.5718, 0.6326, 0.4147, 0.3556, 0.5623, 0.4833, 0.4971, 0.5262, 0.4087, 0.5021]) + # fmt: on + + generated_slice = generated_image.flatten() + generated_slice = torch.cat([generated_slice[:8], generated_slice[-8:]]) + assert torch.allclose(generated_slice, expected_slice, atol=5e-3) + def test_inference_batch_single_identical(self): super().test_inference_batch_single_identical(expected_max_diff=1e-1) @@ -177,21 +190,3 @@ def test_true_cfg_without_negative_prompt_embeds_mask(self): class TestQwenImagePipelineMemory(QwenImagePipelineTesterConfig, MemoryTesterMixin): pass - - -class TestQwenImagePipelinePyramidAttentionBroadcast( - QwenImagePipelineTesterConfig, PyramidAttentionBroadcastTesterMixin -): - pass - - -class TestQwenImagePipelineFirstBlockCache(QwenImagePipelineTesterConfig, FirstBlockCacheTesterMixin): - pass - - -class TestQwenImagePipelineTaylorSeerCache(QwenImagePipelineTesterConfig, TaylorSeerCacheTesterMixin): - pass - - -class TestQwenImagePipelineMagCache(QwenImagePipelineTesterConfig, MagCacheTesterMixin): - pass From 6f7e3869aead86021c8da56eb6949c5d6bd254ce Mon Sep 17 00:00:00 2001 From: Akshan Krithick Date: Fri, 17 Jul 2026 22:26:35 -0700 Subject: [PATCH 5/5] drop batch-single override in qwenimage tests, base tolerance covers it --- tests/pipelines/qwenimage/test_qwenimage.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/pipelines/qwenimage/test_qwenimage.py b/tests/pipelines/qwenimage/test_qwenimage.py index a66dbfbaa026..95ad085fd67c 100644 --- a/tests/pipelines/qwenimage/test_qwenimage.py +++ b/tests/pipelines/qwenimage/test_qwenimage.py @@ -139,9 +139,6 @@ def test_inference(self): generated_slice = torch.cat([generated_slice[:8], generated_slice[-8:]]) assert torch.allclose(generated_slice, expected_slice, atol=5e-3) - def test_inference_batch_single_identical(self): - super().test_inference_batch_single_identical(expected_max_diff=1e-1) - def test_vae_tiling(self, expected_diff_max: float = 0.2): pipe = self.pipeline_class(**self.get_dummy_components()).to(torch_device) pipe.set_progress_bar_config(disable=None)